commit 7925d1cda3e6fd40134c16b34bc3cb796a75df17 Author: Melledy <52122272+Melledy@users.noreply.github.com> Date: Sun Apr 17 05:43:07 2022 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fc8d20d9 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b09cd785 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..f7b74cdd --- /dev/null +++ b/README.md @@ -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 diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..c7c9eae7 --- /dev/null +++ b/build.gradle @@ -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(".") +} + diff --git a/data/Banners.json b/data/Banners.json new file mode 100644 index 00000000..163319f5 --- /dev/null +++ b/data/Banners.json @@ -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] + } +] \ No newline at end of file diff --git a/data/query_cur_region.txt b/data/query_cur_region.txt new file mode 100644 index 00000000..427c13f4 --- /dev/null +++ b/data/query_cur_region.txt @@ -0,0 +1 @@ +GpgdCgw0Ny44OS4xNTIuNDcQ1awBGitodHRwczovL29zdXNhb2FzZXJ2ZXIueXVhbnNoZW4uY29tL3JlY2hhcmdlOgNVU0FCOWh0dHBzOi8vYXV0b3BhdGNoaGsueXVhbnNoZW4uY29tL2NsaWVudF9nYW1lX3Jlcy8yLjZfbGl2ZUo8aHR0cHM6Ly9hdXRvcGF0Y2hoay55dWFuc2hlbi5jb20vY2xpZW50X2Rlc2lnbl9kYXRhLzIuNl9saXZlUpIBaHR0cHM6Ly93ZWJzdGF0aWMtc2VhLmhveW92ZXJzZS5jb20veXMvZXZlbnQvaW0tc2VydmljZS9pbmRleC5odG1sP2ltX291dD1mYWxzZSZzaWduX3R5cGU9MiZhdXRoX2FwcGlkPWltX2NjcyZhdXRoa2V5X3Zlcj0xJndpbl9kaXJlY3Rpb249cG9ydHJhaXRiCDIuNl9saXZlcNnsmgOQAdnsmgOaAVx7InJlbW90ZU5hbWUiOiAiZGF0YV92ZXJzaW9ucyIsICJtZDUiOiAiMWE0NDVhZTQ4ZmJkYmUwMzgzMTJiZTg2OGYyODEzZDIiLCAiZmlsZVNpemUiOiA0NDE1faIBW3sicmVtb3RlTmFtZSI6ICJkYXRhX3ZlcnNpb25zIiwgIm1kNSI6ICIxNzU0MmM3YmJhYzQ5YjkxMWRlMjlhOTYyNzU0YmQ2MSIsICJmaWxlU2l6ZSI6IDUxNH2yAYEGCL23mQMa4AV7InJlbW90ZU5hbWUiOiAicmVzX3ZlcnNpb25zX2V4dGVybmFsIiwgIm1kNSI6ICI5ZjA5MmNhMTMwNjdjYWU0MzEzNDkzZWVkM2QzZTlhZSIsICJmaWxlU2l6ZSI6IDUyNjk2Nn0NCnsicmVtb3RlTmFtZSI6ICJyZXNfdmVyc2lvbnNfbWVkaXVtIiwgIm1kNSI6ICI3Yzk0MmU3MDRhODA0YzIxMjJmYzYzZWU5MjhlNzE0OCIsICJmaWxlU2l6ZSI6IDI4NTU1MH0NCnsicmVtb3RlTmFtZSI6ICJyZXNfdmVyc2lvbnNfc3RyZWFtaW5nIiwgIm1kNSI6ICJlMzVmNmQ4NTNkMDRjZTAyMmFjN2MzNmQ0M2Y0MGU3YyIsICJmaWxlU2l6ZSI6IDEyMjAzNn0NCnsicmVtb3RlTmFtZSI6ICJyZWxlYXNlX3Jlc192ZXJzaW9uc19leHRlcm5hbCIsICJtZDUiOiAiODc2NGIwZjJlZDgxNWNkNDQzMGUwODVjNDYxYTZmNGQiLCAiZmlsZVNpemUiOiA1MjY5NjZ9DQp7InJlbW90ZU5hbWUiOiAicmVsZWFzZV9yZXNfdmVyc2lvbnNfbWVkaXVtIiwgIm1kNSI6ICIxYmJlMzc0YzE2YmZmNDU5MTU5OWMyMTk3MzQyMDM0OCIsICJmaWxlU2l6ZSI6IDI4NTU1MH0NCnsicmVtb3RlTmFtZSI6ICJyZWxlYXNlX3Jlc192ZXJzaW9uc19zdHJlYW1pbmciLCAibWQ1IjogIjcyZmE3ZmY2NmQ1MTRmY2JhMzRhZjAwN2YyYjljMmY4IiwgImZpbGVTaXplIjogMTIyMDM2fQ0KeyJyZW1vdGVOYW1lIjogImJhc2VfcmV2aXNpb24iLCAibWQ1IjogImZiZmE2ZDZlMDcwMmQxYzc5ZTA1NjRmYjI4NjdlNzM4IiwgImZpbGVTaXplIjogMTh9IgEwKgo3YTM0YTM2YTZlMggyLjZfbGl2ZboBnBBFYzJiEAAAAJGCjvgHMrTsh3MtgsH5frMACAAAw460k//m++1MxIEXUCck/33uRkbXh2qC29/AivwLhKxa+XHVSAv0dKF5drsBoAKPy4OFKI9DJhCysPt3+RKmbXVvdgaktucaz4GU7/r1wurEHHyf+edEsopDvbCea4nbJVe9+qYHXwBPLepzFNymMWVp9eSkiySwB7aXOMLuWo7utTYk1t3BV+sc7C78f/aPIfGdD/s3XcTQzzEBPYu4FBG4D6PZ8oTGGvg0mWt5q/k2qmEcF8CdzUrJ38l/TiQuNSrWG3s/ALdDwXooplsCEl92sprxZswgpfKIsoPUuVSGUAIPOHY23+Yzx/j0AaMIUbeZB6mwGqffcNtW1qSbeeJWr/2HG9jbdBlr/wnPpDFdGn4oAzsuacaCYGMO8vkU20Lpwn7I3fce3H9zfmDqmroKE5d6tiB3+e212+jgft3b24tdudYGIbFVG7a3+DQYHtSDT5BOKQgbKs4Pw4Lks8vYprrIOHwfxHALjO6YlqkMJcbYPYYUCE3aitVgMFoLztZkVYEBSx6AQOEHG/PkpuDSnmhkDxvNzX8PvmwhEGWROY5kTaDm81bmLrMbf9AYBy5g2ZKP5Vvw6nLwfVrS7gesNPT30JZPHzrZCiUHB79eoWK6hSSlQX1b7z6/qkIFV06X+pgp2lqA4mauJYUm3sx6wmgeJaxDP/I6RpU1EiRFb3TzxkTGo3Wafp2PDg0q8sjj7A+xXREjf3WgsdwPXnCDByOLTV9u4Gd+7KwRZwK/9OChKNU5xXte4VQMf3TvNjBl07AQEtyBQ2swCO7YsiWx3EIv2oqy/zl1QEnVj8BVcfATwghu9vxY2oQ2J7ejxCEtIDRKJXFg8ziB7H1NaWknzlzDOC80UqfWKFCH8fC7KB3ysuAPpN4I0i1y8dRcZdiHK3aImhSs7GXDyr5BpG35RfZk1cBqIl1L8+mWZYKxXinKzKccYXl2JZFvkr+TWDjBoVIBDQBa1RjWPHGF3OT7KWiV6zsHchzVUe7re6WU7PhzRDTB/mw+kxsmcIuuMB2cTsbk29TgScQGd1IkQnzUPUtGYoQiv0i6JbAJFc0nMLl0tMds4xAPAefL2lqfTMIMbJufiohBBMvaEnchohkmDtrGGTuLvBWYSNtlSuSmC8GOlenCUG4bXMWM+ZHwnIA/GsmNsQR1wSlaXFNGnp/Qnw/q7I3Btyayp5Cf3PGLFMdho502/NcY5puROnctNAQKf4+5zUnfnhvqUK3DAWl9Ei5CtTUJ5Vopuh0HFTk0mEjgFutDHjQlXC3F3JWVpIUn22fZHlal2l/evB9sZVJxsMcEEoOrbMmHqmYd4JIdnLMhy9uPZCUoPc+LzrYFXXprNw0XPhFkI4ZtWM95Uz4NfuFWw3d+Ijd3szFMA3WvtZ9Hs1XbdI7sHrRGRIaGn9lNyygofOZhXHQC2ojbRyUOllJQYVUvRB18IxEIHitfD+Q6pUY7FM4TsYoSkoVIjJkQcIv4dsQLyG8StWSL4b4dJZEIi8tk9BQcY52/goO/FbvjB/Rz+cZd/r6J7akGskONwtUBlqe3i+PihvIUbfTA2AwRuaTgpvLwFkOxXRj4Uj0lTExB2to89IRkJ/eADa9eawoh0xswAUxHpmsiUoxRz3UF4gEzpVq4XmmOp2baE2+Uj4rtkzvH5dyODYO39FGudvwZwf6baOlFAKxyBfaVS+W4/udYnLKqoyT7fcuxOqx8cxxLZI6KuDs3nrOe/U9yxIgpvxZSZEejyWVvvRVTzKN3QzMorVXwoPyMlthJpFbAoRt+VPOwMUOAPo3Hhy/6iP03wmUUUBUf4SSSeYoOqXzvj60founyl3ugmpvdEMS2weyTUxq7gkR7xIfAnLzYsb1eLjy0oiRbHGu1QkwhKtqMwjuDfyOcUavzczbNizQHVVORfsiZTMuzLUufVkBug9LrmYkTkUgNs0dCkKQI3RsVP90Kix+gLYmbnIRAhlJbmgzbKULA9ipT3VU3aLXV5/o097pAvpKJGHsk8ibC3yCP2vf2mPLibBHIjGM1T0+sq682kjC1vsJWXl2JVMiDQbHW/zamNixPnU7uTriojCUYLTHt3M5D5d1IxIUIzWyTY6+9zFlO3BBZUCYOaqE0B4ncMfrV9rkuKLay69dPMQLuqF5MxD8oTA1HFDPUEvpFiUhNrxm2VSl7joxjf7TwYkLgDaDKzp5Cmbj4EdCvnRuZ8S+XtzremqnQkwfcbPxkS8GyQxVex9jxrBbz2dicI1hkfvi3hu8qqFJ87/Ozeg9RWjPpLd/Ax5tE6wCh6HO8FUMKO8gKMSFqaTM4i36AN8isOVm2jbKPfWm2Cx8Fwh4VdyKPJ/33FbPTn9dC9ox1/wcDHAta6wGqk224B3QJa6s+gKT1/qb+HObz1i9yRFEcneJkpkt6kTcws1kfoPeQUAwayy9p7Fj1l30TW9oIZwDdyA9746c/bR2dZt0zQtGXsynhjom87f9T2AfvK36EoWQA4hxiZvsKn6E+V4Q229t7FgP0j1oqF7iu+hlgl6IYkmzhf/LVdQJbGslS6Tpa/TpLsFXRnCX/rWBovU01jyvM0w7f7Yyc0S6NcCULtDH2Znqd3JKYAphuWOLWLQlDJg/CRSpK1x4V+hWNuTXETafVlk5ft9do6Et9krcFyHKzATI1SaapornUYv0rgL6hN9y+IZl1CyuL/uyWy9iqLmO5tjx9gitKlCT4Hcj6QWH6Wfg+zmAVraPVjvzCAU9odHRwczovL3dlYnN0YXRpYy1zZWEuaG95b3ZlcnNlLmNvbS95cy9ldmVudC9lMjAyMDA0MTBnb19jb21tdW5pdHkvaW5kZXguaHRtbCMv0gEKYWRmZWI4YmU3MdoBCmFkZmViOGJlNzH6AR1odHRwczovL2FjY291bnQuaG95b3ZlcnNlLmNvbYICcWh0dHBzOi8vaGs0ZS1hcGktb3MuaG95b3ZlcnNlLmNvbS9jb21tb24vYXBpY2RrZXkvYXBpL2V4Y2hhbmdlQ2RrZXk/c2lnbl90eXBlPTImYXV0aF9hcHBpZD1hcGljZGtleSZhdXRoa2V5X3Zlcj0xigJMaHR0cHM6Ly9hY2NvdW50LmhveW92ZXJzZS5jb20vIy9hYm91dC9wcml2YWN5SW5HYW1lP2FwcF9pZD00JmJpej1oazRlX2dsb2JhbFqcEEVjMmIQAAAAkYKO+AcytOyHcy2Cwfl+swAIAADDjrST/+b77UzEgRdQJyT/fe5GRteHaoLb38CK/AuErFr5cdVIC/R0oXl2uwGgAo/Lg4Uoj0MmELKw+3f5EqZtdW92BqS25xrPgZTv+vXC6sQcfJ/550SyikO9sJ5ridslV736pgdfAE8t6nMU3KYxZWn15KSLJLAHtpc4wu5aju61NiTW3cFX6xzsLvx/9o8h8Z0P+zddxNDPMQE9i7gUEbgPo9nyhMYa+DSZa3mr+TaqYRwXwJ3NSsnfyX9OJC41KtYbez8At0PBeiimWwISX3aymvFmzCCl8oiyg9S5VIZQAg84djbf5jPH+PQBowhRt5kHqbAap99w21bWpJt54lav/Ycb2Nt0GWv/Cc+kMV0afigDOy5pxoJgYw7y+RTbQunCfsjd9x7cf3N+YOqaugoTl3q2IHf57bXb6OB+3dvbi1251gYhsVUbtrf4NBge1INPkE4pCBsqzg/DguSzy9imusg4fB/EcAuM7piWqQwlxtg9hhQITdqK1WAwWgvO1mRVgQFLHoBA4Qcb8+Sm4NKeaGQPG83Nfw++bCEQZZE5jmRNoObzVuYusxt/0BgHLmDZko/lW/DqcvB9WtLuB6w09PfQlk8fOtkKJQcHv16hYrqFJKVBfVvvPr+qQgVXTpf6mCnaWoDiZq4lhSbezHrCaB4lrEM/8jpGlTUSJEVvdPPGRMajdZp+nY8ODSryyOPsD7FdESN/daCx3A9ecIMHI4tNX27gZ37srBFnAr/04KEo1TnFe17hVAx/dO82MGXTsBAS3IFDazAI7tiyJbHcQi/airL/OXVASdWPwFVx8BPCCG72/FjahDYnt6PEIS0gNEolcWDzOIHsfU1paSfOXMM4LzRSp9YoUIfx8LsoHfKy4A+k3gjSLXLx1Fxl2IcrdoiaFKzsZcPKvkGkbflF9mTVwGoiXUvz6ZZlgrFeKcrMpxxheXYlkW+Sv5NYOMGhUgENAFrVGNY8cYXc5PspaJXrOwdyHNVR7ut7pZTs+HNENMH+bD6TGyZwi64wHZxOxuTb1OBJxAZ3UiRCfNQ9S0ZihCK/SLolsAkVzScwuXS0x2zjEA8B58vaWp9Mwgxsm5+KiEEEy9oSdyGiGSYO2sYZO4u8FZhI22VK5KYLwY6V6cJQbhtcxYz5kfCcgD8ayY2xBHXBKVpcU0aen9CfD+rsjcG3JrKnkJ/c8YsUx2GjnTb81xjmm5E6dy00BAp/j7nNSd+eG+pQrcMBaX0SLkK1NQnlWim6HQcVOTSYSOAW60MeNCVcLcXclZWkhSfbZ9keVqXaX968H2xlUnGwxwQSg6tsyYeqZh3gkh2csyHL249kJSg9z4vOtgVdems3DRc+EWQjhm1Yz3lTPg1+4VbDd34iN3ezMUwDda+1n0ezVdt0juwetEZEhoaf2U3LKCh85mFcdALaiNtHJQ6WUlBhVS9EHXwjEQgeK18P5DqlRjsUzhOxihKShUiMmRBwi/h2xAvIbxK1ZIvhvh0lkQiLy2T0FBxjnb+Cg78Vu+MH9HP5xl3+vontqQayQ43C1QGWp7eL4+KG8hRt9MDYDBG5pOCm8vAWQ7FdGPhSPSVMTEHa2jz0hGQn94ANr15rCiHTGzABTEemayJSjFHPdQXiATOlWrheaY6nZtoTb5SPiu2TO8fl3I4Ng7f0Ua52/BnB/pto6UUArHIF9pVL5bj+51icsqqjJPt9y7E6rHxzHEtkjoq4Ozees579T3LEiCm/FlJkR6PJZW+9FVPMo3dDMyitVfCg/IyW2EmkVsChG35U87AxQ4A+jceHL/qI/TfCZRRQFR/hJJJ5ig6pfO+PrR+i6fKXe6Cam90QxLbB7JNTGruCRHvEh8CcvNixvV4uPLSiJFsca7VCTCEq2ozCO4N/I5xRq/NzNs2LNAdVU5F+yJlMy7MtS59WQG6D0uuZiRORSA2zR0KQpAjdGxU/3QqLH6AtiZuchECGUluaDNspQsD2KlPdVTdotdXn+jT3ukC+kokYeyTyJsLfII/a9/aY8uJsEciMYzVPT6yrrzaSMLW+wlZeXYlUyINBsdb/NqY2LE+dTu5OuKiMJRgtMe3czkPl3UjEhQjNbJNjr73MWU7cEFlQJg5qoTQHidwx+tX2uS4otrLr108xAu6oXkzEPyhMDUcUM9QS+kWJSE2vGbZVKXuOjGN/tPBiQuANoMrOnkKZuPgR0K+dG5nxL5e3Ot6aqdCTB9xs/GRLwbJDFV7H2PGsFvPZ2JwjWGR++LeG7yqoUnzv87N6D1FaM+kt38DHm0TrAKHoc7wVQwo7yAoxIWppMziLfoA3yKw5WbaNso99abYLHwXCHhV3Io8n/fcVs9Of10L2jHX/BwMcC1rrAaqTbbgHdAlrqz6ApPX+pv4c5vPWL3JEURyd4mSmS3qRNzCzWR+g95BQDBrLL2nsWPWXfRNb2ghnAN3ID3vjpz9tHZ1m3TNC0ZezKeGOibzt/1PYB+8rfoShZADiHGJm+wqfoT5XhDbb23sWA/SPWioXuK76GWCXohiSbOF/8tV1AlsayVLpOlr9OkuwVdGcJf+tYGi9TTWPK8zTDt/tjJzRLo1wJQu0MfZmep3ckpgCmG5Y4tYtCUMmD8JFKkrXHhX6FY25NcRNp9WWTl+312joS32StwXIcrMBMjVJpqmiudRi/SuAvqE33L4hmXULK4v+7JbL2KouY7m2PH2CK0qUJPgdyPpBYfpZ+D7OYBWto9WO/GLVArJ6MMs9QA4I3dIIwrylWFU1xRNtPj3ZUiooCQGiArlYzrMmLb09eDW0QedOr3CPLOlcmZroIV9XmnD9YYJSBxv5L8mbNaGWcZkxZM04GrPcOeDTN2pXq/DdB2cHRC0nT6YaQhvAnvVDFhnoBHnJS9B/aGcgHo3mVzWBi2jeeXNyLZaHhkADBRCPGmTwonHJPIig0hnMvCbLB7b5qHpN8uaQBDW4T6cqIZljsbbXUY1maAcu5vmLK8Tq7Vmu74TTQzghjgxVHB6vIS8vhqWKOKyGSrDGLDnGlcHFRlH/Omz49L2EBfYyDPKUBsi+VMP+cvIrky3XqoBUtTuQwXmcCF3LxPyf8SCVwFZSgDHMND7owHO90vRS60lwsO9QkfojEmbs0MTvFrB/FM1CeHxQeSBA74JVmDRNL4efGIopyTt6SltAxc0flyQzHwW4D8oWg6Tm/Na6 \ No newline at end of file diff --git a/data/query_region_list.txt b/data/query_region_list.txt new file mode 100644 index 00000000..b4681aaa --- /dev/null +++ b/data/query_region_list.txt @@ -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== \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..5c2d1cf0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..f04d6a20 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..83f2acfd --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..9618d8d9 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/keys/dispatchKey.bin b/keys/dispatchKey.bin new file mode 100644 index 00000000..29b396f1 Binary files /dev/null and b/keys/dispatchKey.bin differ diff --git a/keys/dispatchSeed.bin b/keys/dispatchSeed.bin new file mode 100644 index 00000000..0100e2c8 Binary files /dev/null and b/keys/dispatchSeed.bin differ diff --git a/keys/secretKey.bin b/keys/secretKey.bin new file mode 100644 index 00000000..0ff2db51 Binary files /dev/null and b/keys/secretKey.bin differ diff --git a/keys/secretKeyBuffer.bin b/keys/secretKeyBuffer.bin new file mode 100644 index 00000000..d767aa91 --- /dev/null +++ b/keys/secretKeyBuffer.bin @@ -0,0 +1 @@ +lt1L ܟ.\pXP"ƀ(a \ No newline at end of file diff --git a/keystore.p12 b/keystore.p12 new file mode 100644 index 00000000..9fa5cf19 Binary files /dev/null and b/keystore.p12 differ diff --git a/lib/fastutil-mini-8.5.6.jar b/lib/fastutil-mini-8.5.6.jar new file mode 100644 index 00000000..cdc372c1 Binary files /dev/null and b/lib/fastutil-mini-8.5.6.jar differ diff --git a/lib/kcp-netty.jar b/lib/kcp-netty.jar new file mode 100644 index 00000000..29db985f Binary files /dev/null and b/lib/kcp-netty.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..49cff6e1 --- /dev/null +++ b/settings.gradle @@ -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' diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java new file mode 100644 index 00000000..1f5e6fd2 --- /dev/null +++ b/src/main/java/emu/grasscutter/Config.java @@ -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"; + } +} diff --git a/src/main/java/emu/grasscutter/GenshinConstants.java b/src/main/java/emu/grasscutter/GenshinConstants.java new file mode 100644 index 00000000..ff46eddb --- /dev/null +++ b/src/main/java/emu/grasscutter/GenshinConstants.java @@ -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"); +} diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java new file mode 100644 index 00000000..9102ba52 --- /dev/null +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -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 + } +} diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java new file mode 100644 index 00000000..77eb92e1 --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -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 ""; +} diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java new file mode 100644 index 00000000..366b5e2e --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -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 list = new HashMap(); + + 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 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 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 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); + } + } +} diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java new file mode 100644 index 00000000..94835d5b --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -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 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; + */ + } + } + } +} diff --git a/src/main/java/emu/grasscutter/data/GenshinData.java b/src/main/java/emu/grasscutter/data/GenshinData.java new file mode 100644 index 00000000..84614618 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/GenshinData.java @@ -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 abilityHashes = new Int2ObjectOpenHashMap<>(); + private static final Map abilityEmbryos = new HashMap<>(); + private static final Map openConfigEntries = new HashMap<>(); + + // ExcelConfigs + private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); + + private static final Int2ObjectMap avatarDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap avatarLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap avatarSkillDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>(); + private static final Int2ObjectMap avatarPromoteDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap proudSkillDataMap = new Int2ObjectOpenHashMap<>(); + + private static final Int2ObjectMap itemDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap reliquaryMainPropDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap reliquarySetDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap weaponLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap weaponPromoteDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap weaponCurveDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap equipAffixDataMap = new Int2ObjectOpenHashMap<>(); + + private static final Int2ObjectMap monsterDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap npcDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap gadgetDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap monsterCurveDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap monsterDescribeDataMap = new Int2ObjectOpenHashMap<>(); + + private static final Int2ObjectMap avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>(); + private static final Int2ObjectMap avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>(); + private static final Int2ObjectMap 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 getAbilityHashes() { + return abilityHashes; + } + + public static Map getAbilityEmbryoInfo() { + return abilityEmbryos; + } + + public static Map getOpenConfigEntries() { + return openConfigEntries; + } + + public static Int2ObjectMap getAvatarDataMap() { + return avatarDataMap; + } + + public static Int2ObjectMap getItemDataMap() { + return itemDataMap; + } + + public static Int2ObjectMap getAvatarSkillDepotDataMap() { + return avatarSkillDepotDataMap; + } + + public static Int2ObjectMap getAvatarSkillDataMap() { + return avatarSkillDataMap; + } + + public static Int2ObjectMap getPlayerLevelDataMap() { + return playerLevelDataMap; + } + + public static Int2ObjectMap getAvatarLevelDataMap() { + return avatarLevelDataMap; + } + + public static Int2ObjectMap getWeaponLevelDataMap() { + return weaponLevelDataMap; + } + + public static Int2ObjectMap getReliquaryAffixDataMap() { + return reliquaryAffixDataMap; + } + + public static Int2ObjectMap getReliquaryMainPropDataMap() { + return reliquaryMainPropDataMap; + } + + public static Int2ObjectMap getWeaponPromoteDataMap() { + return weaponPromoteDataMap; + } + + public static Int2ObjectMap getWeaponCurveDataMap() { + return weaponCurveDataMap; + } + + public static Int2ObjectMap 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 getProudSkillDataMap() { + return proudSkillDataMap; + } + + public static Int2ObjectMap getMonsterDataMap() { + return monsterDataMap; + } + + public static Int2ObjectMap getNpcDataMap() { + return npcDataMap; + } + + public static Int2ObjectMap getGadgetDataMap() { + return gadgetDataMap; + } + + public static Int2ObjectMap getReliquarySetDataMap() { + return reliquarySetDataMap; + } + + public static Int2ObjectMap getEquipAffixDataMap() { + return equipAffixDataMap; + } + + public static Int2ObjectMap getMonsterCurveDataMap() { + return monsterCurveDataMap; + } + + public static Int2ObjectMap getMonsterDescribeDataMap() { + return monsterDescribeDataMap; + } + + public static Int2ObjectMap getAvatarTalentDataMap() { + return avatarTalentDataMap; + } + + public static Int2ObjectMap getAvatarFlycloakDataMap() { + return avatarFlycloakDataMap; + } + + public static Int2ObjectMap getAvatarCostumeDataMap() { + return avatarCostumeDataMap; + } + + public static Int2ObjectMap getAvatarCostumeDataItemIdMap() { + return avatarCostumeDataItemIdMap; + } +} diff --git a/src/main/java/emu/grasscutter/data/GenshinDepot.java b/src/main/java/emu/grasscutter/data/GenshinDepot.java new file mode 100644 index 00000000..4f141269 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/GenshinDepot.java @@ -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> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicAffixDepot = new Int2ObjectOpenHashMap<>(); + + public static void load() { + for (ReliquaryMainPropData data : GenshinData.getReliquaryMainPropDataMap().values()) { + if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { + continue; + } + WeightedList 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 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 depotList = relicMainPropDepot.get(depot); + if (depotList == null) { + return null; + } + return depotList.next(); + } + + public static List getRandomRelicAffixList(int depot) { + return relicAffixDepot.get(depot); + } +} diff --git a/src/main/java/emu/grasscutter/data/GenshinResource.java b/src/main/java/emu/grasscutter/data/GenshinResource.java new file mode 100644 index 00000000..92e06354 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/GenshinResource.java @@ -0,0 +1,12 @@ +package emu.grasscutter.data; + +public abstract class GenshinResource { + + public int getId() { + return 0; + } + + public void onLoad() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java new file mode 100644 index 00000000..dea19e02 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -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> getResourceDefClasses() { + Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName()); + Set classes = reflections.getSubTypesOf(GenshinResource.class); + + List> 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 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 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 map = new TreeMap<>(); + java.lang.reflect.Type type = new TypeToken>() {}.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 config = null; + + try (FileReader fileReader = new FileReader(file)) { + config = Grasscutter.getGsonFactory().fromJson(fileReader, type); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + for (Entry e : config.entrySet()) { + List 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 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; + } +} diff --git a/src/main/java/emu/grasscutter/data/ResourceType.java b/src/main/java/emu/grasscutter/data/ResourceType.java new file mode 100644 index 00000000..c77f61fc --- /dev/null +++ b/src/main/java/emu/grasscutter/data/ResourceType.java @@ -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; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/common/CurveInfo.java b/src/main/java/emu/grasscutter/data/common/CurveInfo.java new file mode 100644 index 00000000..00c3de3f --- /dev/null +++ b/src/main/java/emu/grasscutter/data/common/CurveInfo.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/common/FightPropData.java b/src/main/java/emu/grasscutter/data/common/FightPropData.java new file mode 100644 index 00000000..aee15a6c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/common/FightPropData.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/data/common/ItemParamData.java b/src/main/java/emu/grasscutter/data/common/ItemParamData.java new file mode 100644 index 00000000..9ec70f00 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/common/ItemParamData.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/common/PropGrowCurve.java b/src/main/java/emu/grasscutter/data/common/PropGrowCurve.java new file mode 100644 index 00000000..b8f11233 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/common/PropGrowCurve.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/AbilityEmbryoEntry.java b/src/main/java/emu/grasscutter/data/custom/AbilityEmbryoEntry.java new file mode 100644 index 00000000..9c551014 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/AbilityEmbryoEntry.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/OpenConfigEntry.java b/src/main/java/emu/grasscutter/data/custom/OpenConfigEntry.java new file mode 100644 index 00000000..d0146763 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/OpenConfigEntry.java @@ -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 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; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarCostumeData.java b/src/main/java/emu/grasscutter/data/def/AvatarCostumeData.java new file mode 100644 index 00000000..b45bac9c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarCostumeData.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarCurveData.java b/src/main/java/emu/grasscutter/data/def/AvatarCurveData.java new file mode 100644 index 00000000..da560b2b --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarCurveData.java @@ -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 curveInfos; + + @Override + public int getId() { + return this.Level; + } + + public int getLevel() { + return Level; + } + + public Map getCurveInfos() { + return curveInfos; + } + + @Override + public void onLoad() { + this.curveInfos = new HashMap<>(); + Stream.of(this.CurveInfos).forEach(info -> this.curveInfos.put(info.getType(), info.getValue())); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarData.java b/src/main/java/emu/grasscutter/data/def/AvatarData.java new file mode 100644 index 00000000..dcc50399 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarData.java @@ -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 CandSkillDepotIds; + private long DescTextMapHash; + private String AvatarIdentityType; + private List AvatarPromoteRewardLevelList; + private List 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 PropGrowCurves; + private int Id; + + private Int2ObjectMap 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 getCandSkillDepotIds(){ + return this.CandSkillDepotIds; + } + + public long getDescTextMapHash(){ + return this.DescTextMapHash; + } + + public String getAvatarIdentityType(){ + return this.AvatarIdentityType; + } + + public List getAvatarPromoteRewardLevelList(){ + return this.AvatarPromoteRewardLevelList; + } + + public List 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)); + } + } + } + } +} + diff --git a/src/main/java/emu/grasscutter/data/def/AvatarFlycloakData.java b/src/main/java/emu/grasscutter/data/def/AvatarFlycloakData.java new file mode 100644 index 00000000..29d04cce --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarFlycloakData.java @@ -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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarLevelData.java b/src/main/java/emu/grasscutter/data/def/AvatarLevelData.java new file mode 100644 index 00000000..f7586f69 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarLevelData.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarPromoteData.java b/src/main/java/emu/grasscutter/data/def/AvatarPromoteData.java new file mode 100644 index 00000000..2a40d44a --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarPromoteData.java @@ -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 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 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()]); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarSkillData.java b/src/main/java/emu/grasscutter/data/def/AvatarSkillData.java new file mode 100644 index 00000000..147c22d1 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarSkillData.java @@ -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 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 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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarSkillDepotData.java b/src/main/java/emu/grasscutter/data/def/AvatarSkillDepotData.java new file mode 100644 index 00000000..4beb6ec8 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarSkillDepotData.java @@ -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 Skills; + private List SubSkills; + private List ExtraAbilities; + private List Talents; + private List 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 getSkills(){ + return this.Skills; + } + + public List getSubSkills(){ + return this.SubSkills; + } + + public int getAttackModeSkill(){ + return this.AttackModeSkill; + } + + public List getExtraAbilities(){ + return this.ExtraAbilities; + } + + public List getTalents(){ + return this.Talents; + } + + public String getTalentStarName(){ + return this.TalentStarName; + } + + public List 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; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/def/AvatarTalentData.java b/src/main/java/emu/grasscutter/data/def/AvatarTalentData.java new file mode 100644 index 00000000..6708da8c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/AvatarTalentData.java @@ -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 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()]); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/EquipAffixData.java b/src/main/java/emu/grasscutter/data/def/EquipAffixData.java new file mode 100644 index 00000000..af08276d --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/EquipAffixData.java @@ -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 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()]); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/GadgetData.java b/src/main/java/emu/grasscutter/data/def/GadgetData.java new file mode 100644 index 00000000..ec775486 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/GadgetData.java @@ -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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ItemData.java b/src/main/java/emu/grasscutter/data/def/ItemData.java new file mode 100644 index 00000000..6c87afea --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ItemData.java @@ -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); + } + + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/data/def/MonsterCurveData.java b/src/main/java/emu/grasscutter/data/def/MonsterCurveData.java new file mode 100644 index 00000000..77a44bd6 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/MonsterCurveData.java @@ -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 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())); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/MonsterData.java b/src/main/java/emu/grasscutter/data/def/MonsterData.java new file mode 100644 index 00000000..2cf41bf7 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/MonsterData.java @@ -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 Affix; + private String Ai; + private int[] Equips; + private List 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 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 getAffix() { + return Affix; + } + + public String getAi() { + return Ai; + } + + public int[] getEquips() { + return Equips; + } + + public List 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 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; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/def/MonsterDescribeData.java b/src/main/java/emu/grasscutter/data/def/MonsterDescribeData.java new file mode 100644 index 00000000..6d6271a7 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/MonsterDescribeData.java @@ -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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/NpcData.java b/src/main/java/emu/grasscutter/data/def/NpcData.java new file mode 100644 index 00000000..ad08ca9e --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/NpcData.java @@ -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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/PlayerLevelData.java b/src/main/java/emu/grasscutter/data/def/PlayerLevelData.java new file mode 100644 index 00000000..3735b7e5 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/PlayerLevelData.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ProudSkillData.java b/src/main/java/emu/grasscutter/data/def/ProudSkillData.java new file mode 100644 index 00000000..6ffb180d --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ProudSkillData.java @@ -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 CostItems; + private List FilterConds; + private List 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 getCostItems() { + return CostItems; + } + + public List getFilterConds() { + return FilterConds; + } + + public List 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 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()]); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ReliquaryAffixData.java b/src/main/java/emu/grasscutter/data/def/ReliquaryAffixData.java new file mode 100644 index 00000000..25e7693c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ReliquaryAffixData.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ReliquaryLevelData.java b/src/main/java/emu/grasscutter/data/def/ReliquaryLevelData.java new file mode 100644 index 00000000..0217ff48 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ReliquaryLevelData.java @@ -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 propMap; + + private int Rank; + private int Level; + private int Exp; + private List 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; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ReliquaryMainPropData.java b/src/main/java/emu/grasscutter/data/def/ReliquaryMainPropData.java new file mode 100644 index 00000000..a67233f5 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ReliquaryMainPropData.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/ReliquarySetData.java b/src/main/java/emu/grasscutter/data/def/ReliquarySetData.java new file mode 100644 index 00000000..85c3e323 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/ReliquarySetData.java @@ -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() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/WeaponCurveData.java b/src/main/java/emu/grasscutter/data/def/WeaponCurveData.java new file mode 100644 index 00000000..0bccd603 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/WeaponCurveData.java @@ -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 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())); + } +} diff --git a/src/main/java/emu/grasscutter/data/def/WeaponLevelData.java b/src/main/java/emu/grasscutter/data/def/WeaponLevelData.java new file mode 100644 index 00000000..c3f41568 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/WeaponLevelData.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/WeaponPromoteData.java b/src/main/java/emu/grasscutter/data/def/WeaponPromoteData.java new file mode 100644 index 00000000..ab008bfe --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/WeaponPromoteData.java @@ -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 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 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()]); + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseCounter.java b/src/main/java/emu/grasscutter/database/DatabaseCounter.java new file mode 100644 index 00000000..37fa59d7 --- /dev/null +++ b/src/main/java/emu/grasscutter/database/DatabaseCounter.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java new file mode 100644 index 00000000..6c622c2b --- /dev/null +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -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 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 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 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 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 q = DatabaseManager.getDatastore().createQuery(Account.class).field("username").equalIgnoreCase(username); + return DatabaseManager.getDatastore().findAndDelete(q) != null; + } + + public static GenshinPlayer getPlayerById(int id) { + Query query = DatabaseManager.getDatastore().createQuery(GenshinPlayer.class).field("_id").equal(id); + MorphiaCursor cursor = query.find(FIND_ONE); + if (!cursor.hasNext()) return null; + return cursor.next(); + } + + public static boolean checkPlayerExists(int id) { + MorphiaCursor 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 getAvatars(GenshinPlayer player) { + Query 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 getInventoryItems(GenshinPlayer player) { + Query query = DatabaseManager.getDatastore().createQuery(GenshinItem.class).filter("ownerId", player.getId()); + return query.find().toList(); + } + public static List getFriends(GenshinPlayer player) { + Query query = DatabaseManager.getDatastore().createQuery(Friendship.class).filter("ownerId", player.getId()); + return query.find().toList(); + } + + public static List getReverseFriends(GenshinPlayer player) { + Query 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 query = DatabaseManager.getDatastore().createQuery(Friendship.class); + query.and( + query.criteria("ownerId").equal(friendship.getFriendId()), + query.criteria("friendId").equal(friendship.getOwnerId()) + ); + MorphiaCursor reverseFriendship = query.find(FIND_ONE); + if (!reverseFriendship.hasNext()) return null; + return reverseFriendship.next(); + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java new file mode 100644 index 00000000..5098db3c --- /dev/null +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -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 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()); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java new file mode 100644 index 00000000..0bb2e9e0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/game/CoopRequest.java b/src/main/java/emu/grasscutter/game/CoopRequest.java new file mode 100644 index 00000000..1e885672 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/CoopRequest.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/GenshinPlayer.java b/src/main/java/emu/grasscutter/game/GenshinPlayer.java new file mode 100644 index 00000000..57a56128 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/GenshinPlayer.java @@ -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 properties; + private Set nameCardList; + private Set flyCloakList; + private Set 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 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 coopRequests; + @Transient private final InvokeHandler combatInvokeHandler; + @Transient private final InvokeHandler 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 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 getFlyCloakList() { + return flyCloakList; + } + + public Set getCostumeList() { + return costumeList; + } + + public Set getNameCardList() { + return this.nameCardList; + } + + public MpSettingType getMpSetting() { + return mpSetting; + } + + public synchronized Int2ObjectMap getCoopRequests() { + return coopRequests; + } + + public InvokeHandler getCombatInvokeHandler() { + return this.combatInvokeHandler; + } + + public InvokeHandler 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 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 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; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/InvokeHandler.java b/src/main/java/emu/grasscutter/game/InvokeHandler.java new file mode 100644 index 00000000..f94ea05f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/InvokeHandler.java @@ -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 { + private final List entryListForwardAll; + private final List entryListForwardAllExceptCur; + private final List entryListForwardHost; + private final Class packetClass; + + public InvokeHandler(Class 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(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/TeamInfo.java b/src/main/java/emu/grasscutter/game/TeamInfo.java new file mode 100644 index 00000000..304c06a6 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/TeamInfo.java @@ -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 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 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); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/TeamManager.java b/src/main/java/emu/grasscutter/game/TeamManager.java new file mode 100644 index 00000000..bcbea754 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/TeamManager.java @@ -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 teams; + private int currentTeamIndex; + private int currentCharacterIndex; + + @Transient private TeamInfo mpTeam; + @Transient private int entityId; + @Transient private final List avatars; + @Transient private final List 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 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 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 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 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 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 list) { + // Sanity checks + if (list.size() == 0 || list.size() > getMaxTeamSize() || !getPlayer().isInMultiplayer()) { + return; + } + + TeamInfo teamInfo = this.getMpTeam(); + + // Set team data + LinkedHashSet 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(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/World.java b/src/main/java/emu/grasscutter/game/World.java new file mode 100644 index 00000000..2a5cb6b9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/World.java @@ -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 { + private final GenshinPlayer owner; + private final List players; + + private int levelEntityId; + private int nextEntityId = 0; + private int nextPeerId = 0; + private final Int2ObjectMap 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 getPlayers() { + return players; + } + + public int getPlayerCount() { + return getPlayers().size(); + } + + public Int2ObjectMap 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 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 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 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 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 iterator() { + return getPlayers().iterator(); + } +} diff --git a/src/main/java/emu/grasscutter/game/avatar/AvatarProfileData.java b/src/main/java/emu/grasscutter/game/avatar/AvatarProfileData.java new file mode 100644 index 00000000..5458c175 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/avatar/AvatarProfileData.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/avatar/AvatarStat.java b/src/main/java/emu/grasscutter/game/avatar/AvatarStat.java new file mode 100644 index 00000000..fc20d00d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/avatar/AvatarStat.java @@ -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 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java new file mode 100644 index 00000000..a0be0b27 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java @@ -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 { + private final GenshinPlayer player; + private final Int2ObjectMap avatars; + private final Long2ObjectMap avatarsGuid; + + public AvatarStorage(GenshinPlayer player) { + this.player = player; + this.avatars = new Int2ObjectOpenHashMap<>(); + this.avatarsGuid = new Long2ObjectOpenHashMap<>(); + } + + public GenshinPlayer getPlayer() { + return player; + } + + public Int2ObjectMap 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 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 iterator() { + return getAvatars().values().iterator(); + } +} diff --git a/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java b/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java new file mode 100644 index 00000000..414c73cf --- /dev/null +++ b/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java @@ -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 equips; + @Transient private final Int2FloatOpenHashMap fightProp; + @Transient private final Set bonusAbilityList; + + private Map skillLevelMap; // Talent levels + private Map proudSkillBonusMap; // Talent bonus levels (from const) + private int skillDepotId; + private int coreProudSkillLevel; // Constellation level + private Set talentIdList; // Constellation id list + private Set 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 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 getSkillLevelMap() { + return skillLevelMap; + } + + public Map getProudSkillBonusMap() { + return proudSkillBonusMap; + } + + public Set 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 getTalentIdList() { + return talentIdList; + } + + public int getCoreProudSkillLevel() { + return coreProudSkillLevel; + } + + public void setCoreProudSkillLevel(int constLevel) { + this.coreProudSkillLevel = constLevel; + } + + public Set 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java new file mode 100644 index 00000000..33bac7d0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java new file mode 100644 index 00000000..a46fc26c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java new file mode 100644 index 00000000..4abd982f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java new file mode 100644 index 00000000..da7040b1 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -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) { + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityItem.java b/src/main/java/emu/grasscutter/game/entity/EntityItem.java new file mode 100644 index 00000000..b1938c75 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityItem.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java new file mode 100644 index 00000000..9dd4810b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/GenshinEntity.java b/src/main/java/emu/grasscutter/game/entity/GenshinEntity.java new file mode 100644 index 00000000..c3de3069 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/GenshinEntity.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/friends/FriendsList.java b/src/main/java/emu/grasscutter/game/friends/FriendsList.java new file mode 100644 index 00000000..26c65f26 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/friends/FriendsList.java @@ -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 friends; + private final Int2ObjectMap pendingFriends; + + private boolean loaded = false; + + public FriendsList(GenshinPlayer player) { + this.player = player; + this.friends = new Int2ObjectOpenHashMap(); + this.pendingFriends = new Int2ObjectOpenHashMap(); + } + + public GenshinPlayer getPlayer() { + return player; + } + + public boolean hasLoaded() { + return loaded; + } + + public synchronized Int2ObjectMap getFriends() { + return friends; + } + + public synchronized Int2ObjectMap 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 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 friendships = DatabaseHelper.getReverseFriends(getPlayer()); + for (Friendship friend : friendships) { + friend.setFriendProfile(this.getPlayer()); + friend.save(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/friends/Friendship.java b/src/main/java/emu/grasscutter/game/friends/Friendship.java new file mode 100644 index 00000000..954ab318 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/friends/Friendship.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java b/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java new file mode 100644 index 00000000..647557e0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java new file mode 100644 index 00000000..43e9e120 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java new file mode 100644 index 00000000..692e0926 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -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 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 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 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 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; + } +} diff --git a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java new file mode 100644 index 00000000..2e144226 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaInfo.java b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaInfo.java new file mode 100644 index 00000000..c3aabcb7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaInfo.java @@ -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; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/EquipInventoryTab.java b/src/main/java/emu/grasscutter/game/inventory/EquipInventoryTab.java new file mode 100644 index 00000000..af33558c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/EquipInventoryTab.java @@ -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 items; + private final int maxCapacity; + + public EquipInventoryTab(int maxCapacity) { + this.items = new HashSet(); + 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; + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/EquipType.java b/src/main/java/emu/grasscutter/game/inventory/EquipType.java new file mode 100644 index 00000000..a2de82d5 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/EquipType.java @@ -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 map = new Int2ObjectOpenHashMap<>(); + private static final Map 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/GenshinItem.java b/src/main/java/emu/grasscutter/game/inventory/GenshinItem.java new file mode 100644 index 00000000..b9914644 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/GenshinItem.java @@ -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 affixes; + private int refinement = 0; + + // Relic + private int mainPropId; + private List 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 getAffixes() { + return affixes; + } + + public int getRefinement() { + return refinement; + } + + public void setRefinement(int refinement) { + this.refinement = refinement; + } + + public int getMainPropId() { + return mainPropId; + } + + public List 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 affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + + if (affixList == null) { + return; + } + + // Build blacklist - Dont add same stat as main/sub stat + Set 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 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 affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + + if (affixList == null) { + return; + } + + // Build whitelist + Set 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 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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java new file mode 100644 index 00000000..0bd888e7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -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 { + private final GenshinPlayer player; + + private final Long2ObjectMap store; + private final Int2ObjectMap 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 getItems() { + return store; + } + + public Int2ObjectMap 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 items) { + List 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 items) { + List 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 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 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 iterator() { + return this.getItems().values().iterator(); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/InventoryTab.java b/src/main/java/emu/grasscutter/game/inventory/InventoryTab.java new file mode 100644 index 00000000..72099642 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/InventoryTab.java @@ -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(); +} diff --git a/src/main/java/emu/grasscutter/game/inventory/ItemDef.java b/src/main/java/emu/grasscutter/game/inventory/ItemDef.java new file mode 100644 index 00000000..eabda502 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/ItemDef.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/ItemQuality.java b/src/main/java/emu/grasscutter/game/inventory/ItemQuality.java new file mode 100644 index 00000000..298fc86c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/ItemQuality.java @@ -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 map = new Int2ObjectOpenHashMap<>(); + private static final Map 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/ItemType.java b/src/main/java/emu/grasscutter/game/inventory/ItemType.java new file mode 100644 index 00000000..5ea6d1b7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/ItemType.java @@ -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 map = new Int2ObjectOpenHashMap<>(); + private static final Map 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/MaterialInventoryTab.java b/src/main/java/emu/grasscutter/game/inventory/MaterialInventoryTab.java new file mode 100644 index 00000000..f5ebfc5d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/MaterialInventoryTab.java @@ -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 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; + } +} diff --git a/src/main/java/emu/grasscutter/game/inventory/MaterialType.java b/src/main/java/emu/grasscutter/game/inventory/MaterialType.java new file mode 100644 index 00000000..e65cb09a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/inventory/MaterialType.java @@ -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 map = new Int2ObjectOpenHashMap<>(); + private static final Map 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); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/AccountManager.java b/src/main/java/emu/grasscutter/game/managers/AccountManager.java new file mode 100644 index 00000000..68173dc1 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/AccountManager.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager.java b/src/main/java/emu/grasscutter/game/managers/ChatManager.java new file mode 100644 index 00000000..a896006b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/ChatManager.java @@ -0,0 +1,82 @@ +package emu.grasscutter.game.managers; + +import emu.grasscutter.commands.PlayerCommands; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketPlayerChatNotify; +import emu.grasscutter.server.packet.send.PacketPrivateChatNotify; + +public class ChatManager { + private final GameServer server; + + public ChatManager(GameServer server) { + this.server = server; + } + + public GameServer getServer() { + return server; + } + + public void sendPrivChat(GenshinPlayer player, int targetUid, String message) { + // Sanity checks + if (message == null || message.length() == 0) { + return; + } + + // Check if command + if (message.charAt(0) == '!') { + PlayerCommands.handle(player, message); + return; + } + + // Get target + GenshinPlayer target = getServer().getPlayerById(targetUid); + + if (target == null) { + return; + } + + // Create chat packet + GenshinPacket packet = new PacketPrivateChatNotify(player.getId(), target.getId(), message); + + player.sendPacket(packet); + target.sendPacket(packet); + } + + public void sendPrivChat(GenshinPlayer player, int targetUid, int emote) { + // Get target + GenshinPlayer target = getServer().getPlayerById(targetUid); + + if (target == null) { + return; + } + + // Create chat packet + GenshinPacket packet = new PacketPrivateChatNotify(player.getId(), target.getId(), emote); + + player.sendPacket(packet); + target.sendPacket(packet); + } + + public void sendTeamChat(GenshinPlayer player, int channel, String message) { + // Sanity checks + if (message == null || message.length() == 0) { + return; + } + + // Check if command + if (message.charAt(0) == '!') { + PlayerCommands.handle(player, message); + return; + } + + // Create and send chat packet + player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message)); + } + + public void sendTeamChat(GenshinPlayer player, int channel, int icon) { + // Create and send chat packet + player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon)); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java new file mode 100644 index 00000000..79b3fe81 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -0,0 +1,897 @@ +package emu.grasscutter.game.managers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import emu.grasscutter.data.GenshinData; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.custom.OpenConfigEntry; +import emu.grasscutter.data.def.AvatarPromoteData; +import emu.grasscutter.data.def.AvatarSkillData; +import emu.grasscutter.data.def.AvatarSkillDepotData; +import emu.grasscutter.data.def.WeaponPromoteData; +import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens; +import emu.grasscutter.data.def.AvatarTalentData; +import emu.grasscutter.data.def.ProudSkillData; +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.ItemParamOuterClass.ItemParam; +import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo; +import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify; +import emu.grasscutter.server.packet.send.PacketAvatarPromoteRsp; +import emu.grasscutter.server.packet.send.PacketAvatarPropNotify; +import emu.grasscutter.server.packet.send.PacketAvatarSkillChangeNotify; +import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp; +import emu.grasscutter.server.packet.send.PacketAvatarUnlockTalentNotify; +import emu.grasscutter.server.packet.send.PacketAvatarUpgradeRsp; +import emu.grasscutter.server.packet.send.PacketDestroyMaterialRsp; +import emu.grasscutter.server.packet.send.PacketProudSkillChangeNotify; +import emu.grasscutter.server.packet.send.PacketProudSkillExtraLevelNotify; +import emu.grasscutter.server.packet.send.PacketReliquaryUpgradeRsp; +import emu.grasscutter.server.packet.send.PacketSetEquipLockStateRsp; +import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify; +import emu.grasscutter.server.packet.send.PacketUnlockAvatarTalentRsp; +import emu.grasscutter.server.packet.send.PacketWeaponAwakenRsp; +import emu.grasscutter.server.packet.send.PacketWeaponPromoteRsp; +import emu.grasscutter.server.packet.send.PacketWeaponUpgradeRsp; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + +public class InventoryManager { + private final GameServer server; + + private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction + private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence + + private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore + private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore + private final static int WEAPON_ORE_3 = 104013; // Mystic Enhancement Ore + private final static int WEAPON_ORE_EXP_1 = 400; // Enhancement Ore + private final static int WEAPON_ORE_EXP_2 = 2000; // Fine Enhancement Ore + private final static int WEAPON_ORE_EXP_3 = 10000; // Mystic Enhancement Ore + + private final static int AVATAR_BOOK_1 = 104001; // Wanderer's Advice + private final static int AVATAR_BOOK_2 = 104002; // Adventurer's Experience + private final static int AVATAR_BOOK_3 = 104003; // Hero's Wit + private final static int AVATAR_BOOK_EXP_1 = 1000; // Wanderer's Advice + private final static int AVATAR_BOOK_EXP_2 = 5000; // Adventurer's Experience + private final static int AVATAR_BOOK_EXP_3 = 20000; // Hero's Wit + + public InventoryManager(GameServer server) { + this.server = server; + } + + public GameServer getServer() { + return server; + } + + public void lockEquip(GenshinPlayer player, long targetEquipGuid, boolean isLocked) { + GenshinItem equip = player.getInventory().getItemByGuid(targetEquipGuid); + + if (equip == null || !equip.getItemData().isEquip()) { + return; + } + + equip.setLocked(isLocked); + equip.save(); + + player.sendPacket(new PacketStoreItemChangeNotify(equip)); + player.sendPacket(new PacketSetEquipLockStateRsp(equip)); + } + + public void upgradeRelic(GenshinPlayer player, long targetGuid, List foodRelicList, List list) { + GenshinItem relic = player.getInventory().getItemByGuid(targetGuid); + + if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) { + return; + } + + int moraCost = 0; + int expGain = 0; + + for (long guid : foodRelicList) { + // Add to delete queue + GenshinItem food = player.getInventory().getItemByGuid(guid); + if (food == null || !food.isDestroyable()) { + continue; + } + // Calculate mora cost + moraCost += food.getItemData().getBaseConvExp(); + expGain += food.getItemData().getBaseConvExp(); + // Feeding artifact with exp already + if (food.getTotalExp() > 0) { + expGain += (int) Math.floor(food.getTotalExp() * .8f); + } + } + for (ItemParam itemParam : list) { + GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); + if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { + continue; + } + int amount = Math.min(food.getCount(), itemParam.getCount()); + int gain = 0; + if (food.getItemId() == RELIC_MATERIAL_2) { + gain = 10000 * amount; + } else if (food.getItemId() == RELIC_MATERIAL_1) { + gain = 2500 * amount; + } + expGain += gain; + moraCost += gain; + } + + // Make sure exp gain is valid + if (expGain <= 0) { + return; + } + + // Check mora + if (player.getMora() < moraCost) { + return; + } + player.setMora(player.getMora() - moraCost); + + // Consume food items + for (long guid : foodRelicList) { + GenshinItem food = player.getInventory().getItemByGuid(guid); + if (food == null || !food.isDestroyable()) { + continue; + } + player.getInventory().removeItem(food); + } + for (ItemParam itemParam : list) { + GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); + if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { + continue; + } + int amount = Math.min(food.getCount(), itemParam.getCount()); + player.getInventory().removeItem(food, amount); + } + + // Implement random rate boost + int rate = 1; + int boost = Utils.randomRange(1, 100); + if (boost == 100) { + rate = 5; + } else if (boost <= 9) { + rate = 2; + } + expGain *= rate; + + // Now we upgrade + int level = relic.getLevel(); + int oldLevel = level; + int exp = relic.getExp(); + int totalExp = relic.getTotalExp(); + int reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); + int upgrades = 0; + List oldAppendPropIdList = relic.getAppendPropIdList(); + + while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) { + // Do calculations + int toGain = Math.min(expGain, reqExp - exp); + exp += toGain; + totalExp += toGain; + expGain -= toGain; + // Level up + if (exp >= reqExp) { + // Exp + exp = 0; + level += 1; + // On relic levelup + if (relic.getItemData().getAddPropLevelSet() != null && relic.getItemData().getAddPropLevelSet().contains(level)) { + upgrades += 1; + } + // Set req exp + reqExp = GenshinData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); + } + } + + if (upgrades > 0) { + oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList()); + while (upgrades > 0) { + relic.addAppendProp(); + upgrades -= 1; + } + } + + // Save + relic.setLevel(level); + relic.setExp(exp); + relic.setTotalExp(totalExp); + relic.save(); + + // Avatar + if (oldLevel != level) { + GenshinAvatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null; + if (avatar != null) { + avatar.recalcStats(); + } + } + + // Packet + player.sendPacket(new PacketStoreItemChangeNotify(relic)); + player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList)); + } + + public List calcWeaponUpgradeReturnItems(GenshinPlayer player, long targetGuid, List foodWeaponGuidList, List itemParamList) { + GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); + + // Sanity checks + if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { + return null; + } + + WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); + if (promoteData == null) { + return null; + } + + // Get exp gain + int expGain = 0; + for (long guid : foodWeaponGuidList) { + GenshinItem food = player.getInventory().getItemByGuid(guid); + if (food == null) { + continue; + } + expGain += food.getItemData().getWeaponBaseExp(); + if (food.getTotalExp() > 0) { + expGain += (int) Math.floor(food.getTotalExp() * .8f); + } + } + for (ItemParam param : itemParamList) { + GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); + if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { + continue; + } + int amount = Math.min(param.getCount(), food.getCount()); + if (food.getItemId() == WEAPON_ORE_3) { + expGain += 10000 * amount; + } else if (food.getItemId() == WEAPON_ORE_2) { + expGain += 2000 * amount; + } else if (food.getItemId() == WEAPON_ORE_1) { + expGain += 400 * amount; + } + } + + // Try + int maxLevel = promoteData.getUnlockMaxLevel(); + int level = weapon.getLevel(); + int exp = weapon.getExp(); + int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); + + while (expGain > 0 && reqExp > 0 && level < maxLevel) { + // Do calculations + int toGain = Math.min(expGain, reqExp - exp); + exp += toGain; + expGain -= toGain; + // Level up + if (exp >= reqExp) { + // Exp + exp = 0; + level += 1; + // Set req exp + reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); + } + } + + return getLeftoverOres(expGain); + } + + + public void upgradeWeapon(GenshinPlayer player, long targetGuid, List foodWeaponGuidList, List itemParamList) { + GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); + + // Sanity checks + if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { + return; + } + + WeaponPromoteData promoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); + if (promoteData == null) { + return; + } + + // Get exp gain + int expGain = 0, moraCost = 0; + + for (long guid : foodWeaponGuidList) { + GenshinItem food = player.getInventory().getItemByGuid(guid); + if (food == null || !food.isDestroyable()) { + continue; + } + expGain += food.getItemData().getWeaponBaseExp(); + moraCost += (int) Math.floor(food.getItemData().getWeaponBaseExp() * .1f); + if (food.getTotalExp() > 0) { + expGain += (int) Math.floor(food.getTotalExp() * .8f); + } + } + for (ItemParam param : itemParamList) { + GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); + if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { + continue; + } + int amount = Math.min(param.getCount(), food.getCount()); + int gain = 0; + if (food.getItemId() == WEAPON_ORE_3) { + gain = 10000 * amount; + } else if (food.getItemId() == WEAPON_ORE_2) { + gain = 2000 * amount; + } else if (food.getItemId() == WEAPON_ORE_1) { + gain = 400 * amount; + } + expGain += gain; + moraCost += (int) Math.floor(gain * .1f); + } + + // Make sure exp gain is valid + if (expGain <= 0) { + return; + } + + // Mora check + if (player.getMora() >= moraCost) { + player.setMora(player.getMora() - moraCost); + } else { + return; + } + + // Consume weapon/items used to feed + for (long guid : foodWeaponGuidList) { + GenshinItem food = player.getInventory().getItemByGuid(guid); + if (food == null || !food.isDestroyable()) { + continue; + } + player.getInventory().removeItem(food); + } + for (ItemParam param : itemParamList) { + GenshinItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); + if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { + continue; + } + int amount = Math.min(param.getCount(), food.getCount()); + player.getInventory().removeItem(food, amount); + } + + // Level up + int maxLevel = promoteData.getUnlockMaxLevel(); + int level = weapon.getLevel(); + int oldLevel = level; + int exp = weapon.getExp(); + int totalExp = weapon.getTotalExp(); + int reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); + + while (expGain > 0 && reqExp > 0 && level < maxLevel) { + // Do calculations + int toGain = Math.min(expGain, reqExp - exp); + exp += toGain; + totalExp += toGain; + expGain -= toGain; + // Level up + if (exp >= reqExp) { + // Exp + exp = 0; + level += 1; + // Set req exp + reqExp = GenshinData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level); + } + } + + List leftovers = getLeftoverOres(expGain); + player.getInventory().addItemParams(leftovers); + + weapon.setLevel(level); + weapon.setExp(exp); + weapon.setTotalExp(totalExp); + weapon.save(); + + // Avatar + if (oldLevel != level) { + GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; + if (avatar != null) { + avatar.recalcStats(); + } + } + + // Packets + player.sendPacket(new PacketStoreItemChangeNotify(weapon)); + player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers)); + } + + private List getLeftoverOres(float leftover) { + List leftoverOreList = new ArrayList<>(3); + + if (leftover < WEAPON_ORE_EXP_1) { + return leftoverOreList; + } + + // Get leftovers + int ore3 = (int) Math.floor(leftover / WEAPON_ORE_EXP_3); + leftover = leftover % WEAPON_ORE_EXP_3; + int ore2 = (int) Math.floor(leftover / WEAPON_ORE_EXP_2); + leftover = leftover % WEAPON_ORE_EXP_2; + int ore1 = (int) Math.floor(leftover / WEAPON_ORE_EXP_1); + + if (ore3 > 0) { + leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build()); + } if (ore2 > 0) { + leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_2).setCount(ore2).build()); + } if (ore1 > 0) { + leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_1).setCount(ore1).build()); + } + + return leftoverOreList; + } + + public void refineWeapon(GenshinPlayer player, long targetGuid, long feedGuid) { + GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); + GenshinItem feed = player.getInventory().getItemByGuid(feedGuid); + + // Sanity checks + if (weapon == null || feed == null || !feed.isDestroyable()) { + return; + } + + if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemId() != feed.getItemId()) { + return; + } + + if (weapon.getRefinement() >= 4 || weapon.getAffixes() == null || weapon.getAffixes().size() == 0) { + return; + } + + // Calculate + int oldRefineLevel = weapon.getRefinement(); + int targetRefineLevel = Math.min(oldRefineLevel + feed.getRefinement() + 1, 4); + int moraCost = 0; + + try { + moraCost = weapon.getItemData().getAwakenCosts()[weapon.getRefinement()]; + } catch (Exception e) { + return; + } + + // Mora check + if (player.getMora() >= moraCost) { + player.setMora(player.getMora() - moraCost); + } else { + return; + } + + // Consume weapon + player.getInventory().removeItem(feed); + + // Get + weapon.setRefinement(targetRefineLevel); + weapon.save(); + + // Avatar + GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; + if (avatar != null) { + avatar.recalcStats(); + } + + // Packets + player.sendPacket(new PacketStoreItemChangeNotify(weapon)); + player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel)); + } + + public void promoteWeapon(GenshinPlayer player, long targetGuid) { + GenshinItem weapon = player.getInventory().getItemByGuid(targetGuid); + + if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) { + return; + } + + int nextPromoteLevel = weapon.getPromoteLevel() + 1; + WeaponPromoteData currentPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); + WeaponPromoteData nextPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel); + if (currentPromoteData == null || nextPromoteData == null) { + return; + } + + // Level check + if (weapon.getLevel() != currentPromoteData.getUnlockMaxLevel()) { + return; + } + + // Make sure player has promote items + for (ItemParamData cost : nextPromoteData.getCostItems()) { + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + if (feedItem == null || feedItem.getCount() < cost.getCount()) { + return; + } + } + + // Mora check + if (player.getMora() >= nextPromoteData.getCoinCost()) { + player.setMora(player.getMora() - nextPromoteData.getCoinCost()); + } else { + return; + } + + // Consume promote filler items + for (ItemParamData cost : nextPromoteData.getCostItems()) { + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + player.getInventory().removeItem(feedItem, cost.getCount()); + } + + int oldPromoteLevel = weapon.getPromoteLevel(); + weapon.setPromoteLevel(nextPromoteLevel); + weapon.save(); + + // Avatar + GenshinAvatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null; + if (avatar != null) { + avatar.recalcStats(); + } + + // Packets + player.sendPacket(new PacketStoreItemChangeNotify(weapon)); + player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel)); + } + + public void promoteAvatar(GenshinPlayer player, long guid) { + GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); + + // Sanity checks + if (avatar == null) { + return; + } + + int nextPromoteLevel = avatar.getPromoteLevel() + 1; + AvatarPromoteData currentPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel()); + AvatarPromoteData nextPromoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel); + if (currentPromoteData == null || nextPromoteData == null) { + return; + } + + // Level check + if (avatar.getLevel() != currentPromoteData.getUnlockMaxLevel()) { + return; + } + + // Make sure player has cost items + for (ItemParamData cost : nextPromoteData.getCostItems()) { + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + if (feedItem == null || feedItem.getCount() < cost.getCount()) { + return; + } + } + + // Mora check + if (player.getMora() >= nextPromoteData.getCoinCost()) { + player.setMora(player.getMora() - nextPromoteData.getCoinCost()); + } else { + return; + } + + // Consume promote filler items + for (ItemParamData cost : nextPromoteData.getCostItems()) { + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + player.getInventory().removeItem(feedItem, cost.getCount()); + } + + // Update promote level + avatar.setPromoteLevel(nextPromoteLevel); + + // Update proud skills + AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()); + boolean hasAddedProudSkill = false; + + if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) { + for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { + if (openData.getProudSkillGroupId() == 0) { + continue; + } + if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) { + int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; + if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) { + hasAddedProudSkill = true; + avatar.getProudSkillList().add(proudSkillId); + player.sendPacket(new PacketProudSkillChangeNotify(avatar)); + } + } + } + } + + // Racalc stats and save avatar + avatar.recalcStats(); + avatar.save(); + + // Resend ability embryos if proud skill has been added + if (hasAddedProudSkill && avatar.getAsEntity() != null) { + player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity())); + } + + // TODO Send entity prop update packet to world + + // Packets + player.sendPacket(new PacketAvatarPropNotify(avatar)); + player.sendPacket(new PacketAvatarPromoteRsp(avatar)); + } + + public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) { + GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); + + // Sanity checks + if (avatar == null) { + return; + } + + AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel()); + if (promoteData == null) { + return; + } + + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); + + if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) { + return; + } + + // Calc exp + int expGain = 0, moraCost = 0; + + // TODO clean up + if (itemId == AVATAR_BOOK_3) { + expGain = AVATAR_BOOK_EXP_3 * count; + } else if (itemId == AVATAR_BOOK_2) { + expGain = AVATAR_BOOK_EXP_2 * count; + } else if (itemId == AVATAR_BOOK_1) { + expGain = AVATAR_BOOK_EXP_1 * count; + } + moraCost = (int) Math.floor(expGain * .2f); + + // Mora check + if (player.getMora() >= moraCost) { + player.setMora(player.getMora() - moraCost); + } else { + return; + } + + // Consume items + player.getInventory().removeItem(feedItem, count); + + // Level up + int maxLevel = promoteData.getUnlockMaxLevel(); + int level = avatar.getLevel(); + int oldLevel = level; + int exp = avatar.getExp(); + int reqExp = GenshinData.getAvatarLevelExpRequired(level); + + while (expGain > 0 && reqExp > 0 && level < maxLevel) { + // Do calculations + int toGain = Math.min(expGain, reqExp - exp); + exp += toGain; + expGain -= toGain; + // Level up + if (exp >= reqExp) { + // Exp + exp = 0; + level += 1; + // Set req exp + reqExp = GenshinData.getAvatarLevelExpRequired(level); + } + } + + // Old map for packet + Map oldPropMap = avatar.getFightProperties(); + if (oldLevel != level) { + // Deep copy if level has changed + oldPropMap = avatar.getFightProperties().int2FloatEntrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + // Done + avatar.setLevel(level); + avatar.setExp(exp); + avatar.recalcStats(); + avatar.save(); + + // TODO Send entity prop update packet to world + + // Packets + player.sendPacket(new PacketAvatarPropNotify(avatar)); + player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap)); + } + + public void upgradeAvatarSkill(GenshinPlayer player, long guid, int skillId) { + // Sanity checks + GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); + if (avatar == null) { + return; + } + + // Make sure avatar has skill + if (!avatar.getSkillLevelMap().containsKey(skillId)) { + return; + } + + AvatarSkillData skillData = GenshinData.getAvatarSkillDataMap().get(skillId); + if (skillData == null) { + return; + } + + // Get data for next skill level + int currentLevel = avatar.getSkillLevelMap().get(skillId); + int nextLevel = currentLevel + 1; + int proudSkillId = (skillData.getProudSkillGroupId() * 100) + nextLevel; + + // Capped at level 10 talent + if (nextLevel > 10) { + return; + } + + // Proud skill data + ProudSkillData proudSkill = GenshinData.getProudSkillDataMap().get(proudSkillId); + if (proudSkill == null) { + return; + } + + // Make sure break level is correct + if (avatar.getPromoteLevel() < proudSkill.getBreakLevel()) { + return; + } + + // Make sure player has cost items + for (ItemParamData cost : proudSkill.getCostItems()) { + if (cost.getId() == 0) { + continue; + } + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + if (feedItem == null || feedItem.getCount() < cost.getCount()) { + return; + } + } + + // Mora check + if (player.getMora() >= proudSkill.getCoinCost()) { + player.setMora(player.getMora() - proudSkill.getCoinCost()); + } else { + return; + } + + // Consume promote filler items + for (ItemParamData cost : proudSkill.getCostItems()) { + if (cost.getId() == 0) { + continue; + } + GenshinItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); + player.getInventory().removeItem(feedItem, cost.getCount()); + } + + // Upgrade skill + avatar.getSkillLevelMap().put(skillId, nextLevel); + avatar.save(); + + // Packet + player.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillId, currentLevel, nextLevel)); + player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel)); + } + + public void unlockAvatarConstellation(GenshinPlayer player, long guid) { + // Sanity checks + GenshinAvatar avatar = player.getAvatars().getAvatarByGuid(guid); + if (avatar == null) { + return; + } + + // Get talent + int currentTalentLevel = avatar.getCoreProudSkillLevel(); + int nextTalentId = ((avatar.getAvatarId() % 10000000) * 10) + currentTalentLevel + 1; + AvatarTalentData talentData = GenshinData.getAvatarTalentDataMap().get(nextTalentId); + + if (talentData == null) { + return; + } + + GenshinItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId()); + if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) { + return; + } + + // Consume item + player.getInventory().removeItem(costItem, talentData.getMainCostItemCount()); + + // Apply + recalc + avatar.getTalentIdList().add(talentData.getId()); + avatar.setCoreProudSkillLevel(currentTalentLevel + 1); + avatar.recalcStats(); + + // Packet + player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId)); + player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId)); + + // Proud skill bonus map + OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig()); + if (entry != null && entry.getExtraTalentIndex() > 0) { + avatar.recalcProudSkillBonusMap(); + player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex())); + } + + // Resend ability embryos + if (avatar.getAsEntity() != null) { + player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity())); + } + + // Save avatar + avatar.save(); + } + + public void destroyMaterial(GenshinPlayer player, List list) { + // Return materials + Int2IntOpenHashMap returnMaterialMap = new Int2IntOpenHashMap(); + + for (MaterialInfo info : list) { + // Sanity check + if (info.getCount() <= 0) { + continue; + } + + GenshinItem item = player.getInventory().getItemByGuid(info.getGuid()); + if (item == null || !item.isDestroyable()) { + continue; + } + + // Remove + int removeAmount = Math.min(info.getCount(), item.getCount()); + player.getInventory().removeItem(item, removeAmount); + + // Delete material return items + if (item.getItemData().getDestroyReturnMaterial().length > 0) { + for (int i = 0; i < item.getItemData().getDestroyReturnMaterial().length; i++) { + returnMaterialMap.addTo(item.getItemData().getDestroyReturnMaterial()[i], item.getItemData().getDestroyReturnMaterialCount()[i]); + } + } + } + + // Give back items + if (returnMaterialMap.size() > 0) { + for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) { + player.getInventory().addItem(new GenshinItem(e.getIntKey(), e.getIntValue())); + } + } + + // Packets + player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap)); + } + + public GenshinItem useItem(GenshinPlayer player, long targetGuid, long itemGuid, int count) { + GenshinAvatar target = player.getAvatars().getAvatarByGuid(targetGuid); + GenshinItem useItem = player.getInventory().getItemByGuid(itemGuid); + + if (useItem == null) { + return null; + } + + int used = 0; + + // Use + switch (useItem.getItemData().getMaterialType()) { + case MATERIAL_FOOD: + if (useItem.getItemData().getUseTarget().equals("ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR")) { + if (target == null) { + break; + } + + used = player.getTeamManager().reviveAvatar(target) ? 1 : 0; + } + break; + default: + break; + } + + if (used > 0) { + player.getInventory().removeItem(useItem, used); + return useItem; + } + + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/MultiplayerManager.java b/src/main/java/emu/grasscutter/game/managers/MultiplayerManager.java new file mode 100644 index 00000000..98027069 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MultiplayerManager.java @@ -0,0 +1,153 @@ +package emu.grasscutter.game.managers; + +import emu.grasscutter.game.CoopRequest; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.GenshinPlayer.SceneLoadState; +import emu.grasscutter.game.props.EnterReason; +import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; +import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason; +import emu.grasscutter.game.World; +import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpNotify; +import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify; +import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify; + +public class MultiplayerManager { + private final GameServer server; + + public MultiplayerManager(GameServer server) { + this.server = server; + } + + public GameServer getServer() { + return server; + } + + public void applyEnterMp(GenshinPlayer player, int targetUid) { + GenshinPlayer target = getServer().getPlayerById(targetUid); + if (target == null) { + player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpReason.PlayerCannotEnterMp)); + return; + } + + // Sanity checks - Dont let player join if already in multiplayer + if (player.getWorld().isMultiplayer()) { + return; + } + + if (target.getWorld().isDungeon()) { + player.sendPacket(new PacketPlayerApplyEnterMpResultNotify(targetUid, "", false, PlayerApplyEnterMpReason.SceneCannotEnter)); + return; + } + + // Get request + CoopRequest request = target.getCoopRequests().get(player.getId()); + + if (request != null && !request.isExpired()) { + // Join request already exists + return; + } + + // Put request in + request = new CoopRequest(player); + target.getCoopRequests().put(player.getId(), request); + + // Packet + target.sendPacket(new PacketPlayerApplyEnterMpNotify(player)); + } + + public void applyEnterMpReply(GenshinPlayer player, int applyUid, boolean isAgreed) { + // Checks + CoopRequest request = player.getCoopRequests().get(applyUid); + if (request == null || request.isExpired()) { + return; + } + + // Remove now that we are handling it + GenshinPlayer requester = request.getRequester(); + player.getCoopRequests().remove(applyUid); + + // Sanity checks - Dont let player join if already in multiplayer + if (requester.getWorld().isMultiplayer()) { + request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(player, false, PlayerApplyEnterMpReason.PlayerCannotEnterMp)); + return; + } + + // Response packet + request.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(player, isAgreed, PlayerApplyEnterMpReason.PlayerJudge)); + + // Declined + if (!isAgreed) { + return; + } + + // Success + if (!player.getWorld().isMultiplayer()) { + // Player not in multiplayer, create multiplayer world + World world = new World(player, true); + + // Add + world.addPlayer(player); + + // Rejoin packet + player.sendPacket(new PacketPlayerEnterSceneNotify(player, player, EnterType.EnterSelf, EnterReason.HostFromSingleToMp, player.getWorld().getSceneId(), player.getPos())); + } + + // Make requester join + player.getWorld().addPlayer(requester); + + // Packet + requester.sendPacket(new PacketPlayerEnterSceneNotify(requester, player, EnterType.EnterOther, EnterReason.TeamJoin, player.getWorld().getSceneId(), player.getPos())); + requester.getPos().set(player.getPos()); + requester.getRotation().set(player.getRotation()); + } + + public boolean leaveCoop(GenshinPlayer player) { + // Make sure player's world is multiplayer + if (!player.getWorld().isMultiplayer()) { + return false; + } + + // Make sure everyone's scene is loaded + for (GenshinPlayer p : player.getWorld().getPlayers()) { + if (p.getSceneLoadState() != SceneLoadState.LOADED) { + return false; + } + } + + // Create new world for player + World world = new World(player); + world.addPlayer(player); + + // Packet + player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TeamBack, player.getWorld().getSceneId(), player.getPos())); + + return true; + } + + public boolean kickPlayer(GenshinPlayer player, int targetUid) { + // Make sure player's world is multiplayer and that player is owner + if (!player.getWorld().isMultiplayer() || player.getWorld().getHost() != player) { + return false; + } + + // Get victim and sanity checks + GenshinPlayer victim = player.getServer().getPlayerById(targetUid); + + if (victim == null || victim == player) { + return false; + } + + // Make sure victim's scene has loaded + if (victim.getSceneLoadState() != SceneLoadState.LOADED) { + return false; + } + + // Kick + World world = new World(victim); + world.addPlayer(victim); + + victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getWorld().getSceneId(), victim.getPos())); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ActionReason.java b/src/main/java/emu/grasscutter/game/props/ActionReason.java new file mode 100644 index 00000000..7f38de18 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ActionReason.java @@ -0,0 +1,211 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum ActionReason { + None(0), + QuestItem(1), + QuestReward(2), + Trifle(3), + Shop(4), + PlayerUpgradeReward(5), + AddAvatar(6), + GadgetEnvAnimal(7), + MonsterEnvAnimal(8), + Compound(9), + Cook(10), + Gather(11), + MailAttachment(12), + CityLevelupReturn(15), + CityLevelupReward(17), + AreaExploreReward(18), + UnlockPointReward(19), + DungeonFirstPass(20), + DungeonPass(21), + ChangeElemType(23), + FetterOpen(25), + DailyTaskScore(26), + DailyTaskHost(27), + RandTaskHost(28), + Expedition(29), + Gacha(30), + Combine(31), + RandTaskGuest(32), + DailyTaskGuest(33), + ForgeOutput(34), + ForgeReturn(35), + InitAvatar(36), + MonsterDie(37), + Gm(38), + OpenChest(39), + GadgetDie(40), + MonsterChangeHp(41), + SubfieldDrop(42), + PushTipsReward(43), + ActivityMonsterDrop(44), + ActivityGather(45), + ActivitySubfieldDrop(46), + TowerScheduleReward(47), + TowerFloorStarReward(48), + TowerFirstPassReward(49), + TowerDailyReward(50), + HitClientTrivialEntity(51), + OpenWorldBossChest(52), + MaterialDeleteReturn(53), + SignInReward(54), + OpenBlossomChest(55), + Recharge(56), + BonusActivityReward(57), + TowerCommemorativeReward(58), + TowerSkipFloorReward(59), + RechargeBonus(60), + RechargeCard(61), + RechargeCardDaily(62), + RechargeCardReplace(63), + RechargeCardReplaceFree(64), + RechargePlayReplace(65), + MpPlayTakeReward(66), + ActivityWatcher(67), + SalesmanDeliverItem(68), + SalesmanReward(69), + Rebate(70), + McoinExchangeHcoin(71), + DailyTaskExchangeLegendaryKey(72), + UnlockPersonLine(73), + FetterLevelReward(74), + BuyResin(75), + RechargePackage(76), + DeliveryDailyReward(77), + CityReputationLevel(78), + CityReputationQuest(79), + CityReputationRequest(80), + CityReputationExplore(81), + OffergingLevel(82), + RoutineHost(83), + RoutineGuest(84), + TreasureMapSpotToken(89), + TreasureMapBonusLevelReward(90), + TreasureMapMpReward(91), + Convert(92), + OverflowTransform(93), + ActivityAvatarSelectionReward(96), + ActivityWatcherBatch(97), + HitTreeDrop(98), + GetHomeLevelupReward(99), + HomeDefaultFurniture(100), + ActivityCond(101), + BattlePassNotify(102), + PlayerUseItem(1001), + DropItem(1002), + WeaponUpgrade(1011), + WeaponPromote(1012), + WeaponAwaken(1013), + RelicUpgrade(1014), + Ability(1015), + DungeonStatueDrop(1016), + OfflineMsg(1017), + AvatarUpgrade(1018), + AvatarPromote(1019), + QuestAction(1021), + CityLevelup(1022), + UpgradeSkill(1024), + UnlockTalent(1025), + UpgradeProudSkill(1026), + PlayerLevelLimitUp(1027), + DungeonDaily(1028), + ItemGiving(1030), + ForgeCost(1031), + InvestigationReward(1032), + InvestigationTargetReward(1033), + GadgetInteract(1034), + SeaLampCiMaterial(1036), + SeaLampContributionReward(1037), + SeaLampPhaseReward(1038), + SeaLampFlyLamp(1039), + AutoRecover(1040), + ActivityExpireItem(1041), + SubCoinNegative(1042), + BargainDeduct(1043), + BattlePassPaidReward(1044), + BattlePassLevelReward(1045), + TrialAvatarActivityFirstPassReward(1046), + BuyBattlePassLevel(1047), + GrantBirthdayBenefit(1048), + AchievementReward(1049), + AchievementGoalReward(1050), + FirstShareToSocialNetwork(1051), + DestroyMaterial(1052), + CodexLevelupReward(1053), + HuntingOfferReward(1054), + UseWidgetAnchorPoint(1055), + UseWidgetBonfire(1056), + UngradeWeaponReturnMaterial(1057), + UseWidgetOneoffGatherPointDetector(1058), + UseWidgetClientCollector(1059), + UseWidgetClientDetector(1060), + TakeGeneralReward(1061), + AsterTakeSpecialReward(1062), + RemoveCodexBook(1063), + OfferingItem(1064), + UseWidgetGadgetBuilder(1065), + EffigyFirstPassReward(1066), + EffigyReward(1067), + ReunionFirstGiftReward(1068), + ReunionSignInReward(1069), + ReunionWatcherReward(1070), + SalesmanMpReward(1071), + ActionReasionAvatarPromoteReward(1072), + BlessingRedeemReward(1073), + ActionMiracleRingReward(1074), + ExpeditionReward(1075), + TreasureMapRemoveDetector(1076), + MechanicusDungeonTicket(1077), + MechanicusLevelupGear(1078), + MechanicusBattleSettle(1079), + RegionSearchReward(1080), + UnlockCoopChapter(1081), + TakeCoopReward(1082), + FleurFairDungeonReward(1083), + ActivityScore(1084), + ChannellerSlabOneoffDungeonReward(1085), + FurnitureMakeStart(1086), + FurnitureMakeTake(1087), + FurnitureMakeCancel(1088), + FurnitureMakeFastFinish(1089), + ChannellerSlabLoopDungeonFirstPassReward(1090), + ChannellerSlabLoopDungeonScoreReward(1091), + HomeLimitedShopBuy(1092), + HomeCoinCollect(1093); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private ActionReason(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ActionReason getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static ActionReason getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ClimateType.java b/src/main/java/emu/grasscutter/game/props/ClimateType.java new file mode 100644 index 00000000..433bf4a7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ClimateType.java @@ -0,0 +1,45 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum ClimateType { + CLIMATE_NONE(0), + CLIMATE_SUNNY(1), + CLIMATE_CLOUDY(2), + CLIMATE_RAIN(3), + CLIMATE_THUNDERSTORM(4), + CLIMATE_SNOW(5), + CLIMATE_MIST(6); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private ClimateType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ClimateType getTypeByValue(int value) { + return map.getOrDefault(value, CLIMATE_NONE); + } + + public static ClimateType getTypeByName(String name) { + return stringMap.getOrDefault(name, CLIMATE_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ElementType.java b/src/main/java/emu/grasscutter/game/props/ElementType.java new file mode 100644 index 00000000..23362c39 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ElementType.java @@ -0,0 +1,76 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum ElementType { + None (0, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), + Fire (1, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2"), + Water (2, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2"), + Grass (3, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY), + Electric (4, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2"), + Ice (5, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2"), + Frozen (6, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), + Wind (7, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2"), + Rock (8, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2"), + AntiFire (9, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), + Default (255, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent"); + + private final int value; + private final int teamResonanceId; + private final FightProperty energyProperty; + private final int configHash; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private ElementType(int value, FightProperty energyProperty) { + this(value, energyProperty, 0, null); + } + + private ElementType(int value, FightProperty energyProperty, int teamResonanceId, String configName) { + this.value = value; + this.energyProperty = energyProperty; + this.teamResonanceId = teamResonanceId; + if (configName != null) { + this.configHash = Utils.abilityHash(configName); + } else { + this.configHash = 0; + } + } + + public int getValue() { + return value; + } + + public FightProperty getEnergyProperty() { + return energyProperty; + } + + public int getTeamResonanceId() { + return teamResonanceId; + } + + public int getConfigHash() { + return configHash; + } + + public static ElementType getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static ElementType getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/EnterReason.java b/src/main/java/emu/grasscutter/game/props/EnterReason.java new file mode 100644 index 00000000..39e0e440 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/EnterReason.java @@ -0,0 +1,70 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum EnterReason { + None(0), + Login(1), + DungeonReplay(11), + DungeonReviveOnWaypoint(12), + DungeonEnter(13), + DungeonQuit(14), + Gm(21), + QuestRollback(31), + Revival(32), + PersonalScene(41), + TransPoint(42), + ClientTransmit(43), + ForceDragBack(44), + TeamKick(51), + TeamJoin(52), + TeamBack(53), + Muip(54), + DungeonInviteAccept(55), + Lua(56), + ActivityLoadTerrain(57), + HostFromSingleToMp(58), + MpPlay(59), + AnchorPoint(60), + LuaSkipUi(61), + ReloadTerrain(62), + DraftTransfer(63), + EnterHome(64), + ExitHome(65), + ChangeHomeModule(66), + Gallery(67), + HomeSceneJump(68), + HideAndSeek(69); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private EnterReason(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static EnterReason getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static EnterReason getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/EntityIdType.java b/src/main/java/emu/grasscutter/game/props/EntityIdType.java new file mode 100644 index 00000000..09414b65 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/EntityIdType.java @@ -0,0 +1,21 @@ +package emu.grasscutter.game.props; + +public enum EntityIdType { + AVATAR (0x01), + MONSTER (0x02), + NPC (0x03), + GADGET (0x04), + WEAPON (0x06), + TEAM (0x09), + MPLEVEL (0x0b); + + private final int id; + + private EntityIdType(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/FightProperty.java b/src/main/java/emu/grasscutter/game/props/FightProperty.java new file mode 100644 index 00000000..e36c432f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/FightProperty.java @@ -0,0 +1,136 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum FightProperty { + 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 map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + public static final int[] fightProps = new int[] {1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 2000, 2001, 2002, 2003, 1010}; + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getId(), e); + stringMap.put(e.name(), e); + }); + } + + private FightProperty(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static FightProperty getPropById(int value) { + return map.getOrDefault(value, FIGHT_PROP_NONE); + } + + public static FightProperty getPropByName(String name) { + return stringMap.getOrDefault(name, FIGHT_PROP_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/GrowCurve.java b/src/main/java/emu/grasscutter/game/props/GrowCurve.java new file mode 100644 index 00000000..e3a369f3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/GrowCurve.java @@ -0,0 +1,100 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum GrowCurve { + GROW_CURVE_NONE(0), + GROW_CURVE_HP(1), + GROW_CURVE_ATTACK(2), + GROW_CURVE_STAMINA(3), + GROW_CURVE_STRIKE(4), + GROW_CURVE_ANTI_STRIKE(5), + GROW_CURVE_ANTI_STRIKE1(6), + GROW_CURVE_ANTI_STRIKE2(7), + GROW_CURVE_ANTI_STRIKE3(8), + GROW_CURVE_STRIKE_HURT(9), + GROW_CURVE_ELEMENT(10), + GROW_CURVE_KILL_EXP(11), + GROW_CURVE_DEFENSE(12), + GROW_CURVE_ATTACK_BOMB(13), + GROW_CURVE_HP_LITTLEMONSTER(14), + GROW_CURVE_ELEMENT_MASTERY(15), + GROW_CURVE_PROGRESSION(16), + GROW_CURVE_DEFENDING(17), + GROW_CURVE_MHP(18), + GROW_CURVE_MATK(19), + GROW_CURVE_TOWERATK(20), + GROW_CURVE_HP_S5(21), + GROW_CURVE_HP_S4(22), + GROW_CURVE_HP_2(23), + GROW_CURVE_ATTACK_S5(31), + GROW_CURVE_ATTACK_S4(32), + GROW_CURVE_ATTACK_S3(33), + GROW_CURVE_STRIKE_S5(34), + GROW_CURVE_DEFENSE_S5(41), + GROW_CURVE_DEFENSE_S4(42), + GROW_CURVE_ATTACK_101(1101), + GROW_CURVE_ATTACK_102(1102), + GROW_CURVE_ATTACK_103(1103), + GROW_CURVE_ATTACK_104(1104), + GROW_CURVE_ATTACK_105(1105), + GROW_CURVE_ATTACK_201(1201), + GROW_CURVE_ATTACK_202(1202), + GROW_CURVE_ATTACK_203(1203), + GROW_CURVE_ATTACK_204(1204), + GROW_CURVE_ATTACK_205(1205), + GROW_CURVE_ATTACK_301(1301), + GROW_CURVE_ATTACK_302(1302), + GROW_CURVE_ATTACK_303(1303), + GROW_CURVE_ATTACK_304(1304), + GROW_CURVE_ATTACK_305(1305), + GROW_CURVE_CRITICAL_101(2101), + GROW_CURVE_CRITICAL_102(2102), + GROW_CURVE_CRITICAL_103(2103), + GROW_CURVE_CRITICAL_104(2104), + GROW_CURVE_CRITICAL_105(2105), + GROW_CURVE_CRITICAL_201(2201), + GROW_CURVE_CRITICAL_202(2202), + GROW_CURVE_CRITICAL_203(2203), + GROW_CURVE_CRITICAL_204(2204), + GROW_CURVE_CRITICAL_205(2205), + GROW_CURVE_CRITICAL_301(2301), + GROW_CURVE_CRITICAL_302(2302), + GROW_CURVE_CRITICAL_303(2303), + GROW_CURVE_CRITICAL_304(2304), + GROW_CURVE_CRITICAL_305(2305); + + private final int id; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + public static final int[] fightProps = new int[] {1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 2000, 2001, 2002, 2003, 1010}; + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getId(), e); + stringMap.put(e.name(), e); + }); + } + + private GrowCurve(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static GrowCurve getPropById(int value) { + return map.getOrDefault(value, GROW_CURVE_NONE); + } + + public static GrowCurve getPropByName(String name) { + return stringMap.getOrDefault(name, GROW_CURVE_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/LifeState.java b/src/main/java/emu/grasscutter/game/props/LifeState.java new file mode 100644 index 00000000..3d3ba79e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/LifeState.java @@ -0,0 +1,42 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum LifeState { + LIFE_NONE(0), + LIFE_ALIVE(1), + LIFE_DEAD(2), + LIFE_REVIVE(3); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private LifeState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static LifeState getTypeByValue(int value) { + return map.getOrDefault(value, LIFE_NONE); + } + + public static LifeState getTypeByName(String name) { + return stringMap.getOrDefault(name, LIFE_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/OpenState.java b/src/main/java/emu/grasscutter/game/props/OpenState.java new file mode 100644 index 00000000..7cba2fcf --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/OpenState.java @@ -0,0 +1,204 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum OpenState { + OPEN_STATE_NONE(0), + OPEN_STATE_PAIMON(1), + OPEN_STATE_PAIMON_NAVIGATION(2), + OPEN_STATE_AVATAR_PROMOTE(3), + OPEN_STATE_AVATAR_TALENT(4), + OPEN_STATE_WEAPON_PROMOTE(5), + OPEN_STATE_WEAPON_AWAKEN(6), + OPEN_STATE_QUEST_REMIND(7), + OPEN_STATE_GAME_GUIDE(8), + OPEN_STATE_COOK(9), + OPEN_STATE_WEAPON_UPGRADE(10), + OPEN_STATE_RELIQUARY_UPGRADE(11), + OPEN_STATE_RELIQUARY_PROMOTE(12), + OPEN_STATE_WEAPON_PROMOTE_GUIDE(13), + OPEN_STATE_WEAPON_CHANGE_GUIDE(14), + OPEN_STATE_PLAYER_LVUP_GUIDE(15), + OPEN_STATE_FRESHMAN_GUIDE(16), + OPEN_STATE_SKIP_FRESHMAN_GUIDE(17), + OPEN_STATE_GUIDE_MOVE_CAMERA(18), + OPEN_STATE_GUIDE_SCALE_CAMERA(19), + OPEN_STATE_GUIDE_KEYBOARD(20), + OPEN_STATE_GUIDE_MOVE(21), + OPEN_STATE_GUIDE_JUMP(22), + OPEN_STATE_GUIDE_SPRINT(23), + OPEN_STATE_GUIDE_MAP(24), + OPEN_STATE_GUIDE_ATTACK(25), + OPEN_STATE_GUIDE_FLY(26), + OPEN_STATE_GUIDE_TALENT(27), + OPEN_STATE_GUIDE_RELIC(28), + OPEN_STATE_GUIDE_RELIC_PROM(29), + OPEN_STATE_COMBINE(30), + OPEN_STATE_GACHA(31), + OPEN_STATE_GUIDE_GACHA(32), + OPEN_STATE_GUIDE_TEAM(33), + OPEN_STATE_GUIDE_PROUD(34), + OPEN_STATE_GUIDE_AVATAR_PROMOTE(35), + OPEN_STATE_GUIDE_ADVENTURE_CARD(36), + OPEN_STATE_FORGE(37), + OPEN_STATE_GUIDE_BAG(38), + OPEN_STATE_EXPEDITION(39), + OPEN_STATE_GUIDE_ADVENTURE_DAILYTASK(40), + OPEN_STATE_GUIDE_ADVENTURE_DUNGEON(41), + OPEN_STATE_TOWER(42), + OPEN_STATE_WORLD_STAMINA(43), + OPEN_STATE_TOWER_FIRST_ENTER(44), + OPEN_STATE_RESIN(45), + OPEN_STATE_LIMIT_REGION_FRESHMEAT(47), + OPEN_STATE_LIMIT_REGION_GLOBAL(48), + OPEN_STATE_MULTIPLAYER(49), + OPEN_STATE_GUIDE_MOUSEPC(50), + OPEN_STATE_GUIDE_MULTIPLAYER(51), + OPEN_STATE_GUIDE_DUNGEONREWARD(52), + OPEN_STATE_GUIDE_BLOSSOM(53), + OPEN_STATE_AVATAR_FASHION(54), + OPEN_STATE_PHOTOGRAPH(55), + OPEN_STATE_GUIDE_KSLQUEST(56), + OPEN_STATE_PERSONAL_LINE(57), + OPEN_STATE_GUIDE_PERSONAL_LINE(58), + OPEN_STATE_GUIDE_APPEARANCE(59), + OPEN_STATE_GUIDE_PROCESS(60), + OPEN_STATE_GUIDE_PERSONAL_LINE_KEY(61), + OPEN_STATE_GUIDE_WIDGET(62), + OPEN_STATE_GUIDE_ACTIVITY_SKILL_ASTER(63), + OPEN_STATE_GUIDE_COLDCLIMATE(64), + OPEN_STATE_DERIVATIVE_MALL(65), + OPEN_STATE_GUIDE_EXITMULTIPLAYER(66), + OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD(67), + OPEN_STATE_GUIDE_THEATREMACHANICUS_REBUILD(68), + OPEN_STATE_GUIDE_THEATREMACHANICUS_CARD(69), + OPEN_STATE_GUIDE_THEATREMACHANICUS_MONSTER(70), + OPEN_STATE_GUIDE_THEATREMACHANICUS_MISSION_CHECK(71), + OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD_SELECT(72), + OPEN_STATE_GUIDE_THEATREMACHANICUS_CHALLENGE_START(73), + OPEN_STATE_GUIDE_CONVERT(74), + OPEN_STATE_GUIDE_THEATREMACHANICUS_MULTIPLAYER(75), + OPEN_STATE_GUIDE_COOP_TASK(76), + OPEN_STATE_GUIDE_HOMEWORLD_ADEPTIABODE(77), + OPEN_STATE_GUIDE_HOMEWORLD_DEPLOY(78), + OPEN_STATE_GUIDE_CHANNELLERSLAB_EQUIP(79), + OPEN_STATE_GUIDE_CHANNELLERSLAB_MP_SOLUTION(80), + OPEN_STATE_GUIDE_CHANNELLERSLAB_POWER(81), + OPEN_STATE_GUIDE_HIDEANDSEEK_SKILL(82), + OPEN_STATE_GUIDE_HOMEWORLD_MAPLIST(83), + OPEN_STATE_GUIDE_RELICRESOLVE(84), + OPEN_STATE_GUIDE_GGUIDE(85), + OPEN_STATE_GUIDE_GGUIDE_HINT(86), + OPEN_STATE_CITY_REPUATION_MENGDE(800), + OPEN_STATE_CITY_REPUATION_LIYUE(801), + OPEN_STATE_CITY_REPUATION_UI_HINT(802), + OPEN_STATE_CITY_REPUATION_INAZUMA(803), + OPEN_STATE_SHOP_TYPE_MALL(900), + OPEN_STATE_SHOP_TYPE_RECOMMANDED(901), + OPEN_STATE_SHOP_TYPE_GENESISCRYSTAL(902), + OPEN_STATE_SHOP_TYPE_GIFTPACKAGE(903), + OPEN_STATE_SHOP_TYPE_PAIMON(1001), + OPEN_STATE_SHOP_TYPE_CITY(1002), + OPEN_STATE_SHOP_TYPE_BLACKSMITH(1003), + OPEN_STATE_SHOP_TYPE_GROCERY(1004), + OPEN_STATE_SHOP_TYPE_FOOD(1005), + OPEN_STATE_SHOP_TYPE_SEA_LAMP(1006), + OPEN_STATE_SHOP_TYPE_VIRTUAL_SHOP(1007), + OPEN_STATE_SHOP_TYPE_LIYUE_GROCERY(1008), + OPEN_STATE_SHOP_TYPE_LIYUE_SOUVENIR(1009), + OPEN_STATE_SHOP_TYPE_LIYUE_RESTAURANT(1010), + OPEN_STATE_SHOP_TYPE_INAZUMA_SOUVENIR(1011), + OPEN_STATE_SHOP_TYPE_NPC_TOMOKI(1012), + OPEN_ADVENTURE_MANUAL(1100), + OPEN_ADVENTURE_MANUAL_CITY_MENGDE(1101), + OPEN_ADVENTURE_MANUAL_CITY_LIYUE(1102), + OPEN_ADVENTURE_MANUAL_MONSTER(1103), + OPEN_ADVENTURE_MANUAL_BOSS_DUNGEON(1104), + OPEN_STATE_ACTIVITY_SEALAMP(1200), + OPEN_STATE_ACTIVITY_SEALAMP_TAB2(1201), + OPEN_STATE_ACTIVITY_SEALAMP_TAB3(1202), + OPEN_STATE_BATTLE_PASS(1300), + OPEN_STATE_BATTLE_PASS_ENTRY(1301), + OPEN_STATE_ACTIVITY_CRUCIBLE(1400), + OPEN_STATE_ACTIVITY_NEWBEEBOUNS_OPEN(1401), + OPEN_STATE_ACTIVITY_NEWBEEBOUNS_CLOSE(1402), + OPEN_STATE_ACTIVITY_ENTRY_OPEN(1403), + OPEN_STATE_MENGDE_INFUSEDCRYSTAL(1404), + OPEN_STATE_LIYUE_INFUSEDCRYSTAL(1405), + OPEN_STATE_SNOW_MOUNTAIN_ELDER_TREE(1406), + OPEN_STATE_MIRACLE_RING(1407), + OPEN_STATE_COOP_LINE(1408), + OPEN_STATE_INAZUMA_INFUSEDCRYSTAL(1409), + OPEN_STATE_FISH(1410), + OPEN_STATE_GUIDE_SUMO_TEAM_SKILL(1411), + OPEN_STATE_GUIDE_FISH_RECIPE(1412), + OPEN_STATE_HOME(1500), + OPEN_STATE_ACTIVITY_HOMEWORLD(1501), + OPEN_STATE_ADEPTIABODE(1502), + OPEN_STATE_HOME_AVATAR(1503), + OPEN_STATE_HOME_EDIT(1504), + OPEN_STATE_HOME_EDIT_TIPS(1505), + OPEN_STATE_RELIQUARY_DECOMPOSE(1600), + OPEN_STATE_ACTIVITY_H5(1700), + OPEN_STATE_ORAIONOKAMI(2000), + OPEN_STATE_GUIDE_CHESS_MISSION_CHECK(2001), + OPEN_STATE_GUIDE_CHESS_BUILD(2002), + OPEN_STATE_GUIDE_CHESS_WIND_TOWER_CIRCLE(2003), + OPEN_STATE_GUIDE_CHESS_CARD_SELECT(2004), + OPEN_STATE_INAZUMA_MAINQUEST_FINISHED(2005), + OPEN_STATE_PAIMON_LVINFO(2100), + OPEN_STATE_TELEPORT_HUD(2101), + OPEN_STATE_GUIDE_MAP_UNLOCK(2102), + OPEN_STATE_GUIDE_PAIMON_LVINFO(2103), + OPEN_STATE_GUIDE_AMBORTRANSPORT(2104), + OPEN_STATE_GUIDE_FLY_SECOND(2105), + OPEN_STATE_GUIDE_KAEYA_CLUE(2106), + OPEN_STATE_CAPTURE_CODEX(2107), + OPEN_STATE_ACTIVITY_FISH_OPEN(2200), + OPEN_STATE_ACTIVITY_FISH_CLOSE(2201), + OPEN_STATE_GUIDE_ROGUE_MAP(2205), + OPEN_STATE_GUIDE_ROGUE_RUNE(2206), + OPEN_STATE_GUIDE_BARTENDER_FORMULA(2210), + OPEN_STATE_GUIDE_BARTENDER_MIX(2211), + OPEN_STATE_GUIDE_BARTENDER_CUP(2212), + OPEN_STATE_GUIDE_MAIL_FAVORITES(2400), + OPEN_STATE_GUIDE_POTION_CONFIGURE(2401), + OPEN_STATE_GUIDE_LANV2_FIREWORK(2402), + OPEN_STATE_LOADINGTIPS_ENKANOMIYA(2403), + OPEN_STATE_MICHIAE_CASKET(2500), + OPEN_STATE_MAIL_COLLECT_UNLOCK_RED_POINT(2501), + OPEN_STATE_LUMEN_STONE(2600), + OPEN_STATE_GUIDE_CRYSTALLINK_BUFF(2601); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private OpenState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static OpenState getTypeByValue(int value) { + return map.getOrDefault(value, OPEN_STATE_NONE); + } + + public static OpenState getTypeByName(String name) { + return stringMap.getOrDefault(name, OPEN_STATE_NONE); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java new file mode 100644 index 00000000..9369c160 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -0,0 +1,70 @@ +package emu.grasscutter.game.props; + +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum PlayerProperty { + PROP_EXP (1001), + PROP_BREAK_LEVEL (1002), + PROP_SATIATION_VAL (1003), + PROP_SATIATION_PENALTY_TIME (1004), + PROP_LEVEL (4001), + PROP_LAST_CHANGE_AVATAR_TIME (10001), + PROP_MAX_SPRING_VOLUME (10002), + PROP_CUR_SPRING_VOLUME (10003), + PROP_IS_SPRING_AUTO_USE (10004), + PROP_SPRING_AUTO_USE_PERCENT (10005), + PROP_IS_FLYABLE (10006), + PROP_IS_WEATHER_LOCKED (10007), + PROP_IS_GAME_TIME_LOCKED (10008), + PROP_IS_TRANSFERABLE (10009), + PROP_MAX_STAMINA (10010), + PROP_CUR_PERSIST_STAMINA (10011), + PROP_CUR_TEMPORARY_STAMINA (10012), + PROP_PLAYER_LEVEL (10013), + PROP_PLAYER_EXP (10014), + PROP_PLAYER_HCOIN (10015), // Primogem + PROP_PLAYER_SCOIN (10016), // Mora + PROP_PLAYER_MP_SETTING_TYPE (10017), + PROP_IS_MP_MODE_AVAILABLE (10018), + PROP_PLAYER_WORLD_LEVEL (10019), + PROP_PLAYER_RESIN (10020), + PROP_PLAYER_WAIT_SUB_HCOIN (10022), + PROP_PLAYER_WAIT_SUB_SCOIN (10023), + PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), + PROP_PLAYER_MCOIN (10025), // Genesis Crystal + PROP_PLAYER_WAIT_SUB_MCOIN (10026), + PROP_PLAYER_LEGENDARY_KEY (10027), + PROP_IS_HAS_FIRST_SHARE (10028), + PROP_PLAYER_FORGE_POINT (10029), + PROP_CUR_CLIMATE_METER (10035), + PROP_CUR_CLIMATE_TYPE (10036), + PROP_CUR_CLIMATE_AREA_ID (10037), + PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038), + PROP_PLAYER_WORLD_LEVEL_LIMIT (10039), + PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040), + PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041), + PROP_PLAYER_HOME_COIN (10042), + PROP_PLAYER_WAIT_SUB_HOME_COIN (10043); + + private final int id; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + + static { + Stream.of(values()).forEach(e -> map.put(e.getId(), e)); + } + + private PlayerProperty(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static PlayerProperty getPropById(int value) { + return map.getOrDefault(value, null); + } +} diff --git a/src/main/java/emu/grasscutter/game/shop/ShopInfo.java b/src/main/java/emu/grasscutter/game/shop/ShopInfo.java new file mode 100644 index 00000000..768a3a7c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/shop/ShopInfo.java @@ -0,0 +1,5 @@ +package emu.grasscutter.game.shop; + +public class ShopInfo { + +} diff --git a/src/main/java/emu/grasscutter/game/shop/ShopManager.java b/src/main/java/emu/grasscutter/game/shop/ShopManager.java new file mode 100644 index 00000000..a21888f2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/shop/ShopManager.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.shop; + +import emu.grasscutter.server.game.GameServer; + +public class ShopManager { + private final GameServer server; + + public ShopManager(GameServer server) { + this.server = server; + } + + public GameServer getServer() { + return server; + } +} diff --git a/src/main/java/emu/grasscutter/net/packet/GenshinPacket.java b/src/main/java/emu/grasscutter/net/packet/GenshinPacket.java new file mode 100644 index 00000000..158c0d3c --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/GenshinPacket.java @@ -0,0 +1,141 @@ +package emu.grasscutter.net.packet; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.google.protobuf.GeneratedMessageV3; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.utils.Crypto; + +public class GenshinPacket { + private static final int const1 = 17767; // 0x4567 + private static final int const2 = -30293; // 0x89ab + + private int opcode; + private boolean shouldBuildHeader = false; + + private byte[] header; + private byte[] data; + + // Encryption + private boolean useDispatchKey; + public boolean shouldEncrypt = true; + + public GenshinPacket(int opcode) { + this.opcode = opcode; + } + + public GenshinPacket(int opcode, int clientSequence) { + this.opcode = opcode; + this.buildHeader(clientSequence); + } + + public GenshinPacket(int opcode, boolean buildHeader) { + this.opcode = opcode; + this.shouldBuildHeader = buildHeader; + } + + public int getOpcode() { + return opcode; + } + + public void setOpcode(int opcode) { + this.opcode = opcode; + } + + public boolean useDispatchKey() { + return useDispatchKey; + } + + public void setUseDispatchKey(boolean useDispatchKey) { + this.useDispatchKey = useDispatchKey; + } + + public byte[] getHeader() { + return header; + } + + public void setHeader(byte[] header) { + this.header = header; + } + + public boolean shouldBuildHeader() { + return shouldBuildHeader; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public void setData(GeneratedMessageV3 proto) { + this.data = proto.toByteArray(); + } + + @SuppressWarnings("rawtypes") + public void setData(GeneratedMessageV3.Builder proto) { + this.data = proto.build().toByteArray(); + } + + public GenshinPacket buildHeader(int clientSequence) { + if (this.getHeader() != null && clientSequence == 0) { + return this; + } + setHeader(PacketHead.newBuilder().setClientSequenceId(clientSequence).setTimestamp(System.currentTimeMillis()).build().toByteArray()); + return this; + } + + public byte[] build() { + if (getHeader() == null) { + this.header = new byte[0]; + } + + if (getData() == null) { + this.data = new byte[0]; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(2 + 2 + 2 + 4 + getHeader().length + getData().length + 2); + + this.writeUint16(baos, const1); + this.writeUint16(baos, opcode); + this.writeUint16(baos, header.length); + this.writeUint32(baos, data.length); + this.writeBytes(baos, header); + this.writeBytes(baos, data); + this.writeUint16(baos, const2); + + byte[] packet = baos.toByteArray(); + + if (this.shouldEncrypt) { + Crypto.xor(packet, this.useDispatchKey() ? Crypto.DISPATCH_KEY : Crypto.ENCRYPT_KEY); + } + + return packet; + } + + public void writeUint16(ByteArrayOutputStream baos, int i) { + // Unsigned short + baos.write((byte) ((i >>> 8) & 0xFF)); + baos.write((byte) (i & 0xFF)); + } + + public void writeUint32(ByteArrayOutputStream baos, int i) { + // Unsigned int (long) + baos.write((byte) ((i >>> 24) & 0xFF)); + baos.write((byte) ((i >>> 16) & 0xFF)); + baos.write((byte) ((i >>> 8) & 0xFF)); + baos.write((byte) (i & 0xFF)); + } + + public void writeBytes(ByteArrayOutputStream baos, byte[] bytes) { + try { + baos.write(bytes); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/emu/grasscutter/net/packet/Opcodes.java b/src/main/java/emu/grasscutter/net/packet/Opcodes.java new file mode 100644 index 00000000..0f818e35 --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/Opcodes.java @@ -0,0 +1,13 @@ +package emu.grasscutter.net.packet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Opcodes { + /** Opcode for the packet/handler */ + int value(); + + /** HANDLER ONLY - will disable this handler from being registered */ + boolean disabled() default false; +} diff --git a/src/main/java/emu/grasscutter/net/packet/PacketHandler.java b/src/main/java/emu/grasscutter/net/packet/PacketHandler.java new file mode 100644 index 00000000..c8fbc3a8 --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/PacketHandler.java @@ -0,0 +1,9 @@ +package emu.grasscutter.net.packet; + +import emu.grasscutter.server.game.GameSession; + +public abstract class PacketHandler { + protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + public abstract void handle(GameSession session, byte[] header, byte[] payload) throws Exception; +} diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java new file mode 100644 index 00000000..1399a692 --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -0,0 +1,1211 @@ +package emu.grasscutter.net.packet; + +public class PacketOpcodes { + // Empty + public static final int NONE = 0; + + // Opcodes + public static final int AbilityChangeNotify = 1179; + public static final int AbilityInvocationFailNotify = 1137; + public static final int AbilityInvocationFixedNotify = 1160; + public static final int AbilityInvocationsNotify = 1133; + public static final int AcceptCityReputationRequestReq = 2845; + public static final int AcceptCityReputationRequestRsp = 2875; + public static final int AchievementAllDataNotify = 1155; + public static final int AchievementUpdateNotify = 1146; + public static final int ActivityCoinInfoNotify = 2056; + public static final int ActivityCondStateChangeNotify = 2162; + public static final int ActivityInfoNotify = 2023; + public static final int ActivityPlayOpenAnimNotify = 2164; + public static final int ActivitySaleChangeNotify = 2043; + public static final int ActivityScheduleInfoNotify = 2187; + public static final int ActivitySelectAvatarCardReq = 2153; + public static final int ActivitySelectAvatarCardRsp = 2069; + public static final int ActivityTakeAllScoreRewardRsp = 8836; + public static final int ActivityTakeWatcherRewardBatchReq = 2027; + public static final int ActivityTakeWatcherRewardBatchRsp = 2036; + public static final int ActivityTakeWatcherRewardReq = 2074; + public static final int ActivityTakeWatcherRewardRsp = 2180; + public static final int ActivityUpdateWatcherNotify = 2101; + public static final int AddBlacklistReq = 4067; + public static final int AddBlacklistRsp = 4020; + public static final int AddFriendNotify = 4026; + public static final int AddNoGachaAvatarCardNotify = 1740; + public static final int AddQuestContentProgressReq = 493; + public static final int AddQuestContentProgressRsp = 444; + public static final int AddRandTaskInfoNotify = 147; + public static final int AddSeenMonsterNotify = 242; + public static final int AdjustWorldLevelReq = 104; + public static final int AdjustWorldLevelRsp = 106; + public static final int AllCoopInfoNotify = 1985; + public static final int AllMarkPointNotify = 3462; + public static final int AllSeenMonsterNotify = 276; + public static final int AllWidgetDataNotify = 4284; + public static final int AnchorPointDataNotify = 4285; + public static final int AnchorPointOpReq = 4298; + public static final int AnchorPointOpRsp = 4263; + public static final int AnimatorForceSetAirMoveNotify = 308; + public static final int AntiAddictNotify = 177; + public static final int ArenaChallengeFinishNotify = 2083; + public static final int AskAddFriendNotify = 4062; + public static final int AskAddFriendReq = 4037; + public static final int AskAddFriendRsp = 4093; + public static final int AsterLargeInfoNotify = 2133; + public static final int AsterLittleInfoNotify = 2058; + public static final int AsterMidCampInfoNotify = 2115; + public static final int AsterMidInfoNotify = 2151; + public static final int AsterMiscInfoNotify = 2098; + public static final int AsterProgressInfoNotify = 2065; + public static final int AvatarAddNotify = 1759; + public static final int AvatarBuffAddNotify = 367; + public static final int AvatarBuffDelNotify = 320; + public static final int AvatarCardChangeReq = 667; + public static final int AvatarCardChangeRsp = 620; + public static final int AvatarChangeCostumeNotify = 1748; + public static final int AvatarChangeCostumeReq = 1650; + public static final int AvatarChangeCostumeRsp = 1632; + public static final int AvatarChangeElementTypeReq = 1741; + public static final int AvatarChangeElementTypeRsp = 1626; + public static final int AvatarDataNotify = 1757; + public static final int AvatarDelNotify = 1624; + public static final int AvatarDieAnimationEndReq = 1635; + public static final int AvatarDieAnimationEndRsp = 1638; + public static final int AvatarEnterElementViewNotify = 366; + public static final int AvatarEquipAffixStartNotify = 1734; + public static final int AvatarEquipChangeNotify = 674; + public static final int AvatarExpeditionAllDataReq = 1721; + public static final int AvatarExpeditionAllDataRsp = 1800; + public static final int AvatarExpeditionCallBackReq = 1607; + public static final int AvatarExpeditionCallBackRsp = 1783; + public static final int AvatarExpeditionDataNotify = 1777; + public static final int AvatarExpeditionGetRewardReq = 1604; + public static final int AvatarExpeditionGetRewardRsp = 1731; + public static final int AvatarExpeditionStartReq = 1788; + public static final int AvatarExpeditionStartRsp = 1786; + public static final int AvatarFetterDataNotify = 1718; + public static final int AvatarFetterLevelRewardReq = 1717; + public static final int AvatarFetterLevelRewardRsp = 1690; + public static final int AvatarFightPropNotify = 1237; + public static final int AvatarFightPropUpdateNotify = 1293; + public static final int AvatarFlycloakChangeNotify = 1761; + public static final int AvatarFollowRouteNotify = 3210; + public static final int AvatarGainCostumeNotify = 1778; + public static final int AvatarGainFlycloakNotify = 1676; + public static final int AvatarLifeStateChangeNotify = 1245; + public static final int AvatarPromoteGetRewardReq = 1784; + public static final int AvatarPromoteGetRewardRsp = 1776; + public static final int AvatarPromoteReq = 1661; + public static final int AvatarPromoteRsp = 1712; + public static final int AvatarPropChangeReasonNotify = 1275; + public static final int AvatarPropNotify = 1279; + public static final int AvatarSatiationDataNotify = 1639; + public static final int AvatarSkillChangeNotify = 1088; + public static final int AvatarSkillDepotChangeNotify = 1015; + public static final int AvatarSkillInfoNotify = 1045; + public static final int AvatarSkillMaxChargeCountNotify = 1044; + public static final int AvatarSkillUpgradeReq = 1091; + public static final int AvatarSkillUpgradeRsp = 1097; + public static final int AvatarTeamUpdateNotify = 1649; + public static final int AvatarUnlockTalentNotify = 1010; + public static final int AvatarUpgradeReq = 1660; + public static final int AvatarUpgradeRsp = 1735; + public static final int AvatarWearFlycloakReq = 1677; + public static final int AvatarWearFlycloakRsp = 1782; + public static final int BackMyWorldReq = 219; + public static final int BackMyWorldRsp = 269; + public static final int BargainOfferPriceReq = 409; + public static final int BargainOfferPriceRsp = 465; + public static final int BargainStartNotify = 489; + public static final int BargainTerminateNotify = 403; + public static final int BattlePassAllDataNotify = 2635; + public static final int BattlePassBuySuccNotify = 2612; + public static final int BattlePassCurScheduleUpdateNotify = 2648; + public static final int BattlePassMissionDelNotify = 2645; + public static final int BattlePassMissionUpdateNotify = 2625; + public static final int BeginCameraSceneLookNotify = 261; + public static final int BigTalentPointConvertReq = 1037; + public static final int BigTalentPointConvertRsp = 1093; + public static final int BlessingAcceptAllGivePicReq = 2176; + public static final int BlessingAcceptAllGivePicRsp = 2050; + public static final int BlessingAcceptGivePicReq = 2134; + public static final int BlessingAcceptGivePicRsp = 2117; + public static final int BlessingGetAllRecvPicRecordListReq = 2090; + public static final int BlessingGetAllRecvPicRecordListRsp = 2140; + public static final int BlessingGetFriendPicListReq = 2077; + public static final int BlessingGetFriendPicListRsp = 2182; + public static final int BlessingGiveFriendPicReq = 2161; + public static final int BlessingGiveFriendPicRsp = 2076; + public static final int BlessingRecvFriendPicNotify = 2184; + public static final int BlessingRedeemRewardReq = 2172; + public static final int BlessingRedeemRewardRsp = 2039; + public static final int BlessingScanReq = 2186; + public static final int BlessingScanRsp = 2007; + public static final int BlossomBriefInfoNotify = 2710; + public static final int BlossomChestCreateNotify = 2793; + public static final int BlossomChestInfoNotify = 845; + public static final int BonusActivityInfoReq = 2597; + public static final int BonusActivityInfoRsp = 2588; + public static final int BonusActivityUpdateNotify = 2591; + public static final int BossChestActivateNotify = 844; + public static final int BuyBattlePassLevelReq = 2639; + public static final int BuyBattlePassLevelRsp = 2621; + public static final int BuyGoodsReq = 710; + public static final int BuyGoodsRsp = 715; + public static final int BuyResinReq = 630; + public static final int BuyResinRsp = 647; + public static final int CalcWeaponUpgradeReturnItemsReq = 643; + public static final int CalcWeaponUpgradeReturnItemsRsp = 686; + public static final int CanUseSkillNotify = 1055; + public static final int CancelCityReputationRequestReq = 2834; + public static final int CancelCityReputationRequestRsp = 2879; + public static final int CancelCoopTaskReq = 1989; + public static final int CancelCoopTaskRsp = 1971; + public static final int CancelFinishParentQuestNotify = 492; + public static final int CardProductRewardNotify = 4148; + public static final int ChallengeDataNotify = 983; + public static final int ChallengeRecordNotify = 909; + public static final int ChangeAvatarReq = 1743; + public static final int ChangeAvatarRsp = 1672; + public static final int ChangeGameTimeReq = 175; + public static final int ChangeGameTimeRsp = 134; + public static final int ChangeMailStarNotify = 1497; + public static final int ChangeMpTeamAvatarReq = 1794; + public static final int ChangeMpTeamAvatarRsp = 1629; + public static final int ChangeTeamNameReq = 1793; + public static final int ChangeTeamNameRsp = 1707; + public static final int ChangeWorldToSingleModeNotify = 3293; + public static final int ChangeWorldToSingleModeReq = 3174; + public static final int ChangeWorldToSingleModeRsp = 3308; + public static final int ChapterStateNotify = 455; + public static final int ChatChannelDataNotify = 5047; + public static final int ChatChannelUpdateNotify = 5041; + public static final int ChatHistoryNotify = 3265; + public static final int CheckSegmentCRCNotify = 56; + public static final int CheckSegmentCRCReq = 83; + public static final int ChooseCurAvatarTeamReq = 1713; + public static final int ChooseCurAvatarTeamRsp = 1608; + public static final int CityReputationDataNotify = 2855; + public static final int CityReputationLevelupNotify = 2837; + public static final int ClientAIStateNotify = 1128; + public static final int ClientAbilitiesInitFinishCombineNotify = 1144; + public static final int ClientAbilityChangeNotify = 1191; + public static final int ClientAbilityInitBeginNotify = 1110; + public static final int ClientAbilityInitFinishNotify = 1115; + public static final int ClientCollectorDataNotify = 4262; + public static final int ClientLockGameTimeNotify = 194; + public static final int ClientNewMailNotify = 1434; + public static final int ClientPauseNotify = 278; + public static final int ClientReconnectNotify = 91; + public static final int ClientReportNotify = 28; + public static final int ClientScriptEventNotify = 218; + public static final int ClientTransmitReq = 252; + public static final int ClientTransmitRsp = 292; + public static final int ClientTriggerEventNotify = 197; + public static final int CloseCommonTipsNotify = 3187; + public static final int CodexDataFullNotify = 4204; + public static final int CodexDataUpdateNotify = 4205; + public static final int CombatInvocationsNotify = 347; + public static final int CombineDataNotify = 649; + public static final int CombineFormulaDataNotify = 685; + public static final int CombineReq = 663; + public static final int CombineRsp = 608; + public static final int CompoundDataNotify = 181; + public static final int CookDataNotify = 164; + public static final int CookGradeDataNotify = 166; + public static final int CookRecipeDataNotify = 101; + public static final int CoopCgShowNotify = 1983; + public static final int CoopCgUpdateNotify = 1993; + public static final int CoopChapterUpdateNotify = 1986; + public static final int CoopDataNotify = 1967; + public static final int CoopPointUpdateNotify = 1987; + public static final int CoopProgressUpdateNotify = 2000; + public static final int CoopRewardUpdateNotify = 1976; + public static final int CreateMassiveEntityNotify = 336; + public static final int CreateMassiveEntityReq = 323; + public static final int CreateMassiveEntityRsp = 313; + public static final int CutSceneBeginNotify = 241; + public static final int CutSceneEndNotify = 214; + public static final int CutSceneFinishNotify = 248; + public static final int DailyTaskDataNotify = 124; + public static final int DailyTaskProgressNotify = 161; + public static final int DailyTaskScoreRewardNotify = 138; + public static final int DataResVersionNotify = 136; + public static final int DealAddFriendReq = 4044; + public static final int DealAddFriendRsp = 4045; + public static final int DelMailReq = 1493; + public static final int DelMailRsp = 1444; + public static final int DelScenePlayTeamEntityNotify = 3117; + public static final int DelTeamEntityNotify = 330; + public static final int DeleteFriendNotify = 4083; + public static final int DeleteFriendReq = 4079; + public static final int DeleteFriendRsp = 4091; + public static final int DestroyMassiveEntityNotify = 324; + public static final int DestroyMaterialReq = 670; + public static final int DestroyMaterialRsp = 654; + public static final int DoGachaReq = 1510; + public static final int DoGachaRsp = 1515; + public static final int DoSetPlayerBornDataNotify = 174; + public static final int DraftGuestReplyInviteNotify = 5445; + public static final int DraftGuestReplyInviteReq = 5493; + public static final int DraftGuestReplyInviteRsp = 5444; + public static final int DraftGuestReplyTwiceConfirmNotify = 5488; + public static final int DraftGuestReplyTwiceConfirmReq = 5479; + public static final int DraftGuestReplyTwiceConfirmRsp = 5491; + public static final int DraftInviteResultNotify = 5475; + public static final int DraftOwnerInviteNotify = 5437; + public static final int DraftOwnerStartInviteReq = 5410; + public static final int DraftOwnerStartInviteRsp = 5415; + public static final int DraftOwnerTwiceConfirmNotify = 5434; + public static final int DraftTwiceConfirmResultNotify = 5497; + public static final int DragonSpineChapterFinishNotify = 2196; + public static final int DragonSpineChapterOpenNotify = 2070; + public static final int DragonSpineChapterProgressChangeNotify = 2001; + public static final int DragonSpineCoinChangeNotify = 2189; + public static final int DropHintNotify = 673; + public static final int DropItemReq = 634; + public static final int DropItemRsp = 679; + public static final int DropSubfieldReq = 232; + public static final int DropSubfieldRsp = 251; + public static final int DungeonCandidateTeamChangeAvatarReq = 958; + public static final int DungeonCandidateTeamChangeAvatarRsp = 923; + public static final int DungeonCandidateTeamCreateReq = 964; + public static final int DungeonCandidateTeamCreateRsp = 901; + public static final int DungeonCandidateTeamDismissNotify = 980; + public static final int DungeonCandidateTeamInfoNotify = 965; + public static final int DungeonCandidateTeamInviteNotify = 903; + public static final int DungeonCandidateTeamInviteReq = 966; + public static final int DungeonCandidateTeamInviteRsp = 973; + public static final int DungeonCandidateTeamKickReq = 963; + public static final int DungeonCandidateTeamKickRsp = 908; + public static final int DungeonCandidateTeamLeaveReq = 917; + public static final int DungeonCandidateTeamLeaveRsp = 981; + public static final int DungeonCandidateTeamPlayerLeaveNotify = 920; + public static final int DungeonCandidateTeamRefuseNotify = 967; + public static final int DungeonCandidateTeamReplyInviteReq = 927; + public static final int DungeonCandidateTeamReplyInviteRsp = 902; + public static final int DungeonCandidateTeamSetReadyReq = 952; + public static final int DungeonCandidateTeamSetReadyRsp = 992; + public static final int DungeonChallengeBeginNotify = 974; + public static final int DungeonChallengeFinishNotify = 956; + public static final int DungeonDataNotify = 946; + public static final int DungeonDieOptionReq = 991; + public static final int DungeonDieOptionRsp = 997; + public static final int DungeonEntryInfoReq = 960; + public static final int DungeonEntryInfoRsp = 933; + public static final int DungeonEntryToBeExploreNotify = 3067; + public static final int DungeonFollowNotify = 926; + public static final int DungeonGetStatueDropReq = 962; + public static final int DungeonGetStatueDropRsp = 989; + public static final int DungeonInterruptChallengeReq = 938; + public static final int DungeonInterruptChallengeRsp = 930; + public static final int DungeonPlayerDieNotify = 979; + public static final int DungeonPlayerDieReq = 928; + public static final int DungeonPlayerDieRsp = 955; + public static final int DungeonRestartInviteNotify = 984; + public static final int DungeonRestartInviteReplyNotify = 972; + public static final int DungeonRestartInviteReplyReq = 912; + public static final int DungeonRestartInviteReplyRsp = 953; + public static final int DungeonRestartReq = 932; + public static final int DungeonRestartResultNotify = 970; + public static final int DungeonRestartRsp = 951; + public static final int DungeonSettleNotify = 934; + public static final int DungeonShowReminderNotify = 988; + public static final int DungeonSlipRevivePointActivateReq = 924; + public static final int DungeonSlipRevivePointActivateRsp = 961; + public static final int DungeonWayPointActivateReq = 945; + public static final int DungeonWayPointActivateRsp = 975; + public static final int DungeonWayPointNotify = 944; + public static final int EchoNotify = 62; + public static final int EffigyChallengeInfoNotify = 2159; + public static final int EffigyChallengeResultNotify = 2024; + public static final int EndCameraSceneLookNotify = 238; + public static final int EnterMechanicusDungeonReq = 3979; + public static final int EnterMechanicusDungeonRsp = 3991; + public static final int EnterSceneDoneReq = 268; + public static final int EnterSceneDoneRsp = 290; + public static final int EnterScenePeerNotify = 282; + public static final int EnterSceneReadyReq = 298; + public static final int EnterSceneReadyRsp = 296; + public static final int EnterSceneWeatherAreaNotify = 258; + public static final int EnterTransPointRegionNotify = 255; + public static final int EnterTrialAvatarActivityDungeonReq = 2031; + public static final int EnterTrialAvatarActivityDungeonRsp = 2175; + public static final int EnterWorldAreaReq = 273; + public static final int EnterWorldAreaRsp = 263; + public static final int EntityAiKillSelfNotify = 370; + public static final int EntityAiSyncNotify = 312; + public static final int EntityAuthorityChangeNotify = 303; + public static final int EntityConfigHashNotify = 3458; + public static final int EntityFightPropChangeReasonNotify = 1244; + public static final int EntityFightPropNotify = 1210; + public static final int EntityFightPropUpdateNotify = 1215; + public static final int EntityForceSyncReq = 208; + public static final int EntityForceSyncRsp = 217; + public static final int EntityJumpNotify = 226; + public static final int EntityMoveRoomNotify = 3135; + public static final int EntityPropNotify = 1260; + public static final int EntityTagChangeNotify = 3262; + public static final int EvtAiSyncCombatThreatInfoNotify = 351; + public static final int EvtAiSyncSkillCdNotify = 317; + public static final int EvtAnimatorParameterNotify = 333; + public static final int EvtAnimatorStateChangedNotify = 379; + public static final int EvtAvatarEnterFocusNotify = 389; + public static final int EvtAvatarExitFocusNotify = 309; + public static final int EvtAvatarSitDownNotify = 392; + public static final int EvtAvatarStandUpNotify = 358; + public static final int EvtAvatarUpdateFocusNotify = 365; + public static final int EvtBeingHitNotify = 360; + public static final int EvtBeingHitsCombineNotify = 381; + public static final int EvtBulletDeactiveNotify = 388; + public static final int EvtBulletHitNotify = 397; + public static final int EvtBulletMoveNotify = 362; + public static final int EvtCostStaminaNotify = 375; + public static final int EvtCreateGadgetNotify = 337; + public static final int EvtDestroyGadgetNotify = 393; + public static final int EvtDestroyServerGadgetNotify = 372; + public static final int EvtDoSkillSuccNotify = 315; + public static final int EvtEntityRenderersChangedNotify = 363; + public static final int EvtEntityStartDieEndNotify = 328; + public static final int EvtFaceToDirNotify = 345; + public static final int EvtFaceToEntityNotify = 344; + public static final int EvtRushMoveNotify = 391; + public static final int EvtSetAttackTargetNotify = 334; + public static final int ExecuteGadgetLuaReq = 231; + public static final int ExecuteGadgetLuaRsp = 240; + public static final int ExecuteGroupTriggerReq = 284; + public static final int ExecuteGroupTriggerRsp = 212; + public static final int ExitSceneWeatherAreaNotify = 223; + public static final int ExitTransPointRegionNotify = 246; + public static final int ExpeditionChallengeEnterRegionNotify = 2095; + public static final int ExpeditionChallengeFinishedNotify = 2197; + public static final int ExpeditionRecallReq = 2114; + public static final int ExpeditionRecallRsp = 2108; + public static final int ExpeditionStartReq = 2032; + public static final int ExpeditionStartRsp = 2148; + public static final int ExpeditionTakeRewardReq = 2053; + public static final int ExpeditionTakeRewardRsp = 2181; + public static final int FinishDeliveryNotify = 2126; + public static final int FinishMainCoopReq = 1963; + public static final int FinishMainCoopRsp = 1951; + public static final int FinishedParentQuestNotify = 415; + public static final int FinishedParentQuestUpdateNotify = 437; + public static final int FleurFairBalloonSettleNotify = 2192; + public static final int FleurFairBuffEnergyNotify = 5392; + public static final int FleurFairFallSettleNotify = 2015; + public static final int FleurFairFinishGalleryStageNotify = 5323; + public static final int FleurFairMusicGameSettleReq = 2064; + public static final int FleurFairMusicGameSettleRsp = 2040; + public static final int FleurFairMusicGameStartReq = 2105; + public static final int FleurFairMusicGameStartRsp = 2179; + public static final int FleurFairReplayMiniGameReq = 2146; + public static final int FleurFairReplayMiniGameRsp = 2089; + public static final int FleurFairStageSettleNotify = 5358; + public static final int FlightActivityRestartReq = 2073; + public static final int FlightActivityRestartRsp = 2045; + public static final int FlightActivitySettleNotify = 2195; + public static final int FocusAvatarReq = 1710; + public static final int FocusAvatarRsp = 1772; + public static final int ForceAddPlayerFriendReq = 4084; + public static final int ForceAddPlayerFriendRsp = 4012; + public static final int ForceDragAvatarNotify = 3056; + public static final int ForceDragBackTransferNotify = 3171; + public static final int ForgeDataNotify = 677; + public static final int ForgeFormulaDataNotify = 625; + public static final int ForgeGetQueueDataReq = 681; + public static final int ForgeGetQueueDataRsp = 627; + public static final int ForgeQueueDataNotify = 617; + public static final int ForgeQueueManipulateReq = 692; + public static final int ForgeQueueManipulateRsp = 658; + public static final int ForgeStartReq = 602; + public static final int ForgeStartRsp = 652; + public static final int FoundationNotify = 874; + public static final int FoundationReq = 855; + public static final int FoundationRsp = 846; + public static final int FunitureMakeMakeInfoChangeNotify = 4523; + public static final int FurnitureCurModuleArrangeCountNotify = 4770; + public static final int FurnitureMakeBeHelpedNotify = 4825; + public static final int FurnitureMakeCancelReq = 4495; + public static final int FurnitureMakeCancelRsp = 4814; + public static final int FurnitureMakeFinishNotify = 4707; + public static final int FurnitureMakeHelpReq = 4779; + public static final int FurnitureMakeHelpRsp = 4455; + public static final int FurnitureMakeReq = 4885; + public static final int FurnitureMakeRsp = 4819; + public static final int FurnitureMakeStartReq = 4518; + public static final int FurnitureMakeStartRsp = 4521; + public static final int GMShowNavMeshReq = 2384; + public static final int GMShowNavMeshRsp = 2312; + public static final int GMShowObstacleReq = 2332; + public static final int GMShowObstacleRsp = 2351; + public static final int GadgetAutoPickDropInfoNotify = 888; + public static final int GadgetGeneralRewardInfoNotify = 897; + public static final int GadgetInteractReq = 860; + public static final int GadgetInteractRsp = 833; + public static final int GadgetPlayDataNotify = 879; + public static final int GadgetPlayStartNotify = 875; + public static final int GadgetPlayStopNotify = 834; + public static final int GadgetPlayUidOpNotify = 891; + public static final int GadgetStateNotify = 810; + public static final int GadgetTalkChangeNotify = 856; + public static final int GalleryBalloonScoreNotify = 5510; + public static final int GalleryBalloonShootNotify = 5533; + public static final int GalleryBrokenFloorFallNotify = 5591; + public static final int GalleryBulletHitNotify = 5579; + public static final int GalleryFallCatchNotify = 5537; + public static final int GalleryFallScoreNotify = 5593; + public static final int GalleryFlowerCatchNotify = 5575; + public static final int GalleryPreStartNotify = 5534; + public static final int GalleryStartNotify = 5560; + public static final int GalleryStopNotify = 5515; + public static final int GetActivityInfoReq = 2011; + public static final int GetActivityInfoRsp = 2170; + public static final int GetActivityScheduleReq = 2663; + public static final int GetActivityScheduleRsp = 2651; + public static final int GetActivityShopSheetInfoReq = 744; + public static final int GetActivityShopSheetInfoRsp = 745; + public static final int GetAllActivatedBargainDataReq = 480; + public static final int GetAllActivatedBargainDataRsp = 464; + public static final int GetAllH5ActivityInfoReq = 5675; + public static final int GetAllMailReq = 1479; + public static final int GetAllMailRsp = 1491; + public static final int GetAllSceneGalleryInfoReq = 5544; + public static final int GetAllSceneGalleryInfoRsp = 5545; + public static final int GetAllUnlockNameCardReq = 4065; + public static final int GetAllUnlockNameCardRsp = 4003; + public static final int GetAreaExplorePointReq = 227; + public static final int GetAreaExplorePointRsp = 202; + public static final int GetAuthSalesmanInfoReq = 2082; + public static final int GetAuthSalesmanInfoRsp = 2173; + public static final int GetAuthkeyReq = 1445; + public static final int GetAuthkeyRsp = 1475; + public static final int GetBargainDataReq = 467; + public static final int GetBargainDataRsp = 420; + public static final int GetBattlePassProductReq = 2643; + public static final int GetBattlePassProductRsp = 2626; + public static final int GetBlossomBriefInfoListReq = 2760; + public static final int GetBlossomBriefInfoListRsp = 2733; + public static final int GetBonusActivityRewardReq = 2528; + public static final int GetBonusActivityRewardRsp = 2555; + public static final int GetCityHuntingOfferReq = 4456; + public static final int GetCityHuntingOfferRsp = 4747; + public static final int GetCityReputationInfoReq = 2860; + public static final int GetCityReputationInfoRsp = 2833; + public static final int GetCityReputationMapInfoReq = 2891; + public static final int GetCityReputationMapInfoRsp = 2897; + public static final int GetCompoundDataReq = 127; + public static final int GetCompoundDataRsp = 102; + public static final int GetDailyDungeonEntryInfoReq = 913; + public static final int GetDailyDungeonEntryInfoRsp = 936; + public static final int GetDungeonEntryExploreConditionReq = 3208; + public static final int GetDungeonEntryExploreConditionRsp = 3391; + public static final int GetExpeditionAssistInfoListReq = 2124; + public static final int GetExpeditionAssistInfoListRsp = 2168; + public static final int GetFriendShowAvatarInfoReq = 4061; + public static final int GetFriendShowAvatarInfoRsp = 4038; + public static final int GetFriendShowNameCardInfoReq = 4032; + public static final int GetFriendShowNameCardInfoRsp = 4051; + public static final int GetFurnitureCurModuleArrangeCountReq = 4618; + public static final int GetGachaInfoReq = 1560; + public static final int GetGachaInfoRsp = 1533; + public static final int GetHomeLevelUpRewardReq = 4508; + public static final int GetHomeLevelUpRewardRsp = 4864; + public static final int GetHuntingOfferRewardReq = 4769; + public static final int GetHuntingOfferRewardRsp = 4860; + public static final int GetInvestigationMonsterReq = 1928; + public static final int GetInvestigationMonsterRsp = 1921; + public static final int GetMailItemReq = 1415; + public static final int GetMailItemRsp = 1437; + public static final int GetMapMarkTipsReq = 3307; + public static final int GetMapMarkTipsRsp = 3040; + public static final int GetMechanicusInfoReq = 3960; + public static final int GetMechanicusInfoRsp = 3933; + public static final int GetNextResourceInfoReq = 139; + public static final int GetNextResourceInfoRsp = 187; + public static final int GetOnlinePlayerInfoReq = 46; + public static final int GetOnlinePlayerInfoRsp = 74; + public static final int GetOnlinePlayerListReq = 45; + public static final int GetOnlinePlayerListRsp = 75; + public static final int GetOpActivityInfoReq = 5160; + public static final int GetOpActivityInfoRsp = 5133; + public static final int GetPlayerAskFriendListRsp = 4035; + public static final int GetPlayerBlacklistReq = 4002; + public static final int GetPlayerBlacklistRsp = 4052; + public static final int GetPlayerFriendListReq = 4060; + public static final int GetPlayerFriendListRsp = 4033; + public static final int GetPlayerHomeCompInfoReq = 4540; + public static final int GetPlayerMpModeAvailabilityReq = 1843; + public static final int GetPlayerMpModeAvailabilityRsp = 1826; + public static final int GetPlayerSocialDetailReq = 4075; + public static final int GetPlayerSocialDetailRsp = 4034; + public static final int GetPlayerTokenReq = 160; + public static final int GetPlayerTokenRsp = 133; + public static final int GetPushTipsRewardReq = 2265; + public static final int GetPushTipsRewardRsp = 2203; + public static final int GetQuestTalkHistoryReq = 445; + public static final int GetQuestTalkHistoryRsp = 475; + public static final int GetRecentMpPlayerListReq = 4066; + public static final int GetRecentMpPlayerListRsp = 4073; + public static final int GetRegionSearchReq = 5613; + public static final int GetReunionMissionInfoReq = 5093; + public static final int GetReunionMissionInfoRsp = 5076; + public static final int GetReunionPrivilegeInfoReq = 5089; + public static final int GetReunionPrivilegeInfoRsp = 5071; + public static final int GetReunionSignInInfoReq = 5063; + public static final int GetReunionSignInInfoRsp = 5051; + public static final int GetSceneAreaReq = 262; + public static final int GetSceneAreaRsp = 289; + public static final int GetSceneNpcPositionReq = 515; + public static final int GetSceneNpcPositionRsp = 537; + public static final int GetScenePerformanceReq = 3217; + public static final int GetScenePerformanceRsp = 3319; + public static final int GetScenePointReq = 288; + public static final int GetScenePointRsp = 228; + public static final int GetShopReq = 760; + public static final int GetShopRsp = 733; + public static final int GetShopmallDataReq = 737; + public static final int GetShopmallDataRsp = 793; + public static final int GetSignInRewardReq = 2537; + public static final int GetSignInRewardRsp = 2593; + public static final int GetWidgetSlotReq = 4258; + public static final int GetWidgetSlotRsp = 4294; + public static final int GetWorldMpInfoReq = 3439; + public static final int GetWorldMpInfoRsp = 3424; + public static final int GivingRecordChangeNotify = 172; + public static final int GivingRecordNotify = 153; + public static final int GmTalkReq = 33; + public static final int GmTalkRsp = 10; + public static final int GrantRewardNotify = 680; + public static final int GroupSuiteNotify = 3368; + public static final int GroupUnloadNotify = 3019; + public static final int H5ActivityIdsNotify = 5695; + public static final int HideAndSeekPlayerReadyNotify = 5330; + public static final int HideAndSeekPlayerSetAvatarNotify = 5347; + public static final int HideAndSeekSelectAvatarReq = 5313; + public static final int HideAndSeekSelectAvatarRsp = 5336; + public static final int HideAndSeekSetReadyReq = 5324; + public static final int HideAndSeekSetReadyRsp = 5361; + public static final int HideAndSeekSettleNotify = 5338; + public static final int HitClientTrivialNotify = 250; + public static final int HitTreeNotify = 3432; + public static final int HomeBasicInfoNotify = 4869; + public static final int HomeBlockNotify = 4784; + public static final int HomeChangeEditModeReq = 4483; + public static final int HomeChangeEditModeRsp = 4880; + public static final int HomeChangeModuleReq = 4604; + public static final int HomeChangeModuleRsp = 4631; + public static final int HomeChooseModuleReq = 4567; + public static final int HomeChooseModuleRsp = 4633; + public static final int HomeComfortInfoNotify = 4649; + public static final int HomeGetArrangementInfoReq = 4480; + public static final int HomeGetArrangementInfoRsp = 4781; + public static final int HomeGetBasicInfoReq = 4839; + public static final int HomeGetOnlineStatusReq = 4673; + public static final int HomeGetOnlineStatusRsp = 4626; + public static final int HomeKickPlayerReq = 4547; + public static final int HomeKickPlayerRsp = 4897; + public static final int HomeLimitedShopBuyGoodsReq = 4620; + public static final int HomeLimitedShopBuyGoodsRsp = 4667; + public static final int HomeLimitedShopGoodsListReq = 4706; + public static final int HomeLimitedShopGoodsListRsp = 4587; + public static final int HomeLimitedShopInfoChangeNotify = 4691; + public static final int HomeLimitedShopInfoNotify = 4679; + public static final int HomeLimitedShopInfoReq = 4715; + public static final int HomeLimitedShopInfoRsp = 4808; + public static final int HomeResourceNotify = 4513; + public static final int HomeResourceTakeFetterExpReq = 4884; + public static final int HomeResourceTakeFetterExpRsp = 4608; + public static final int HomeResourceTakeHomeCoinReq = 4812; + public static final int HomeResourceTakeHomeCoinRsp = 4481; + public static final int HomeSceneJumpReq = 4862; + public static final int HomeSceneJumpRsp = 4663; + public static final int HomeUpdateArrangementInfoReq = 4632; + public static final int HomeUpdateArrangementInfoRsp = 4820; + public static final int HostPlayerNotify = 310; + public static final int HuntingFailNotify = 4345; + public static final int HuntingGiveUpReq = 4313; + public static final int HuntingGiveUpRsp = 4301; + public static final int HuntingOngoingNotify = 4348; + public static final int HuntingRevealClueNotify = 4564; + public static final int HuntingRevealFinalNotify = 4335; + public static final int HuntingStartNotify = 4694; + public static final int HuntingSuccessNotify = 4325; + public static final int InBattleMechanicusBuildingPointsNotify = 5344; + public static final int InBattleMechanicusCardResultNotify = 5388; + public static final int InBattleMechanicusConfirmCardNotify = 5397; + public static final int InBattleMechanicusConfirmCardReq = 5379; + public static final int InBattleMechanicusConfirmCardRsp = 5391; + public static final int InBattleMechanicusExcapeMonsterNotify = 5337; + public static final int InBattleMechanicusLeftMonsterNotify = 5393; + public static final int InBattleMechanicusPickCardNotify = 5334; + public static final int InBattleMechanicusPickCardReq = 5345; + public static final int InBattleMechanicusPickCardRsp = 5375; + public static final int InBattleMechanicusSettleNotify = 5355; + public static final int InteractDailyDungeonInfoNotify = 947; + public static final int InterruptGalleryReq = 5597; + public static final int InterruptGalleryRsp = 5588; + public static final int ItemAddHintNotify = 637; + public static final int ItemCdGroupTimeNotify = 666; + public static final int ItemExceedLimitNotify = 639; + public static final int ItemGivingReq = 170; + public static final int ItemGivingRsp = 154; + public static final int JoinHomeWorldFailNotify = 4502; + public static final int JoinPlayerFailNotify = 295; + public static final int JoinPlayerSceneReq = 239; + public static final int JoinPlayerSceneRsp = 287; + public static final int KeepAliveNotify = 60; + public static final int LeaveSceneReq = 233; + public static final int LeaveSceneRsp = 210; + public static final int LevelupCityReq = 253; + public static final int LevelupCityRsp = 272; + public static final int LifeStateChangeNotify = 1233; + public static final int LoadActivityTerrainNotify = 2152; + public static final int LuaSetOptionNotify = 353; + public static final int MailChangeNotify = 1433; + public static final int MainCoopUpdateNotify = 1975; + public static final int MarkEntityInMinMapNotify = 230; + public static final int MarkMapReq = 3053; + public static final int MarkMapRsp = 3139; + public static final int MarkNewNotify = 1291; + public static final int MassiveEntityElementOpBatchNotify = 384; + public static final int MassiveEntityStateChangedNotify = 361; + public static final int MaterialDeleteReturnNotify = 632; + public static final int MaterialDeleteUpdateNotify = 612; + public static final int McoinExchangeHcoinReq = 653; + public static final int McoinExchangeHcoinRsp = 672; + public static final int MechanicusCandidateTeamCreateReq = 3928; + public static final int MechanicusCandidateTeamCreateRsp = 3955; + public static final int MechanicusCloseNotify = 3993; + public static final int MechanicusCoinNotify = 3915; + public static final int MechanicusLevelupGearReq = 3975; + public static final int MechanicusLevelupGearRsp = 3934; + public static final int MechanicusOpenNotify = 3937; + public static final int MechanicusSequenceOpenNotify = 3910; + public static final int MechanicusUnlockGearReq = 3944; + public static final int MechanicusUnlockGearRsp = 3945; + public static final int MeetNpcReq = 544; + public static final int MeetNpcRsp = 545; + public static final int MetNpcIdListNotify = 593; + public static final int MiracleRingDataNotify = 5245; + public static final int MiracleRingDeliverItemReq = 5217; + public static final int MiracleRingDeliverItemRsp = 5236; + public static final int MiracleRingDestroyNotify = 5243; + public static final int MiracleRingDropResultNotify = 5201; + public static final int MiracleRingTakeRewardReq = 5248; + public static final int MiracleRingTakeRewardRsp = 5213; + public static final int MonsterAIConfigHashNotify = 3024; + public static final int MonsterAlertChangeNotify = 380; + public static final int MonsterForceAlertNotify = 364; + public static final int MonsterPointArrayRouteUpdateNotify = 3292; + public static final int MonsterSummonTagNotify = 1360; + public static final int MpBlockNotify = 1824; + public static final int MpPlayGuestReplyInviteReq = 1850; + public static final int MpPlayGuestReplyInviteRsp = 1822; + public static final int MpPlayGuestReplyNotify = 1802; + public static final int MpPlayInviteResultNotify = 1830; + public static final int MpPlayOwnerCheckReq = 1812; + public static final int MpPlayOwnerCheckRsp = 1839; + public static final int MpPlayOwnerInviteNotify = 1831; + public static final int MpPlayOwnerStartInviteReq = 1821; + public static final int MpPlayOwnerStartInviteRsp = 1846; + public static final int MpPlayPrepareInterruptNotify = 1838; + public static final int MpPlayPrepareNotify = 1833; + public static final int MultistagePlayEndNotify = 5307; + public static final int MultistagePlayFinishStageReq = 5333; + public static final int MultistagePlayFinishStageRsp = 5328; + public static final int MultistagePlayInfoNotify = 5360; + public static final int MultistagePlayStageEndNotify = 5321; + public static final int NavMeshStatsNotify = 2353; + public static final int NormalUidOpNotify = 5735; + public static final int NpcTalkReq = 560; + public static final int NpcTalkRsp = 533; + public static final int ObstacleModifyNotify = 2310; + public static final int OneoffGatherPointDetectorDataNotify = 4289; + public static final int OpActivityDataNotify = 5110; + public static final int OpActivityStateNotify = 2560; + public static final int OpActivityUpdateNotify = 5115; + public static final int OpenBlossomCircleCampGuideNotify = 2744; + public static final int OpenStateChangeNotify = 165; + public static final int OpenStateUpdateNotify = 109; + public static final int OrderDisplayNotify = 4101; + public static final int OrderFinishNotify = 4145; + public static final int PSPlayerApplyEnterMpReq = 1837; + public static final int PSPlayerApplyEnterMpRsp = 1806; + public static final int PathfindingEnterSceneReq = 2337; + public static final int PathfindingEnterSceneRsp = 2393; + public static final int PathfindingPingNotify = 2315; + public static final int PersonalLineAllDataReq = 408; + public static final int PersonalLineAllDataRsp = 417; + public static final int PersonalSceneJumpReq = 286; + public static final int PersonalSceneJumpRsp = 277; + public static final int PingReq = 37; + public static final int PingRsp = 93; + public static final int PlatformChangeRouteNotify = 257; + public static final int PlatformStartRouteNotify = 254; + public static final int PlatformStopRouteNotify = 235; + public static final int PlayerAllowEnterMpAfterAgreeMatchNotify = 4176; + public static final int PlayerApplyEnterHomeNotify = 4614; + public static final int PlayerApplyEnterHomeResultNotify = 4580; + public static final int PlayerApplyEnterHomeResultReq = 4603; + public static final int PlayerApplyEnterHomeResultRsp = 4849; + public static final int PlayerApplyEnterMpAfterMatchAgreedNotify = 4177; + public static final int PlayerApplyEnterMpNotify = 1835; + public static final int PlayerApplyEnterMpReq = 1825; + public static final int PlayerApplyEnterMpResultNotify = 1848; + public static final int PlayerApplyEnterMpResultReq = 1813; + public static final int PlayerApplyEnterMpResultRsp = 1801; + public static final int PlayerApplyEnterMpRsp = 1845; + public static final int PlayerCancelMatchReq = 4198; + public static final int PlayerCancelMatchRsp = 4163; + public static final int PlayerChatCDNotify = 3173; + public static final int PlayerChatNotify = 3485; + public static final int PlayerChatReq = 3403; + public static final int PlayerChatRsp = 3045; + public static final int PlayerCompoundMaterialReq = 173; + public static final int PlayerCompoundMaterialRsp = 163; + public static final int PlayerConfirmMatchReq = 4186; + public static final int PlayerConfirmMatchRsp = 4193; + public static final int PlayerCookArgsReq = 135; + public static final int PlayerCookArgsRsp = 157; + public static final int PlayerCookReq = 103; + public static final int PlayerCookRsp = 167; + public static final int PlayerDataNotify = 145; + public static final int PlayerEnterDungeonReq = 910; + public static final int PlayerEnterDungeonRsp = 915; + public static final int PlayerEnterSceneInfoNotify = 294; + public static final int PlayerEnterSceneNotify = 260; + public static final int PlayerEyePointStateNotify = 3461; + public static final int PlayerForceExitReq = 125; + public static final int PlayerForceExitRsp = 149; + public static final int PlayerGameTimeNotify = 179; + public static final int PlayerGeneralMatchConfirmNotify = 4156; + public static final int PlayerGeneralMatchDismissNotify = 4187; + public static final int PlayerGetForceQuitBanInfoReq = 4162; + public static final int PlayerGetForceQuitBanInfoRsp = 4189; + public static final int PlayerHomeCompInfoNotify = 4863; + public static final int PlayerInjectFixNotify = 185; + public static final int PlayerInvestigationAllInfoNotify = 1920; + public static final int PlayerInvestigationNotify = 1901; + public static final int PlayerInvestigationTargetNotify = 1919; + public static final int PlayerLevelRewardUpdateNotify = 112; + public static final int PlayerLoginReq = 110; + public static final int PlayerLoginRsp = 115; + public static final int PlayerLogoutNotify = 144; + public static final int PlayerLogoutReq = 137; + public static final int PlayerLogoutRsp = 193; + public static final int PlayerLuaShellNotify = 143; + public static final int PlayerMatchAgreedResultNotify = 4165; + public static final int PlayerMatchInfoNotify = 4195; + public static final int PlayerMatchStopNotify = 4151; + public static final int PlayerMatchSuccNotify = 4167; + public static final int PlayerOfferingDataNotify = 2915; + public static final int PlayerOfferingReq = 2914; + public static final int PlayerOfferingRsp = 2917; + public static final int PlayerPreEnterMpNotify = 1836; + public static final int PlayerPropChangeNotify = 156; + public static final int PlayerPropChangeReasonNotify = 1234; + public static final int PlayerPropNotify = 191; + public static final int PlayerQuitDungeonReq = 937; + public static final int PlayerQuitDungeonRsp = 993; + public static final int PlayerQuitFromHomeNotify = 4757; + public static final int PlayerQuitFromMpNotify = 1817; + public static final int PlayerRandomCookReq = 120; + public static final int PlayerRandomCookRsp = 180; + public static final int PlayerRechargeDataNotify = 4113; + public static final int PlayerReportReq = 4092; + public static final int PlayerReportRsp = 4058; + public static final int PlayerRoutineDataNotify = 3535; + public static final int PlayerSetLanguageReq = 123; + public static final int PlayerSetLanguageRsp = 113; + public static final int PlayerSetOnlyMPWithPSPlayerReq = 1815; + public static final int PlayerSetOnlyMPWithPSPlayerRsp = 1827; + public static final int PlayerSetPauseReq = 192; + public static final int PlayerSetPauseRsp = 158; + public static final int PlayerStartMatchReq = 4185; + public static final int PlayerStartMatchRsp = 4175; + public static final int PlayerStoreNotify = 660; + public static final int PlayerTimeNotify = 152; + public static final int PostEnterSceneReq = 3390; + public static final int PostEnterSceneRsp = 3213; + public static final int PrivateChatNotify = 4960; + public static final int PrivateChatReq = 5010; + public static final int PrivateChatRsp = 4983; + public static final int PrivateChatSetSequenceReq = 4965; + public static final int PrivateChatSetSequenceRsp = 4987; + public static final int ProudSkillChangeNotify = 1079; + public static final int ProudSkillExtraLevelNotify = 1028; + public static final int ProudSkillUpgradeReq = 1075; + public static final int ProudSkillUpgradeRsp = 1034; + public static final int PullPrivateChatReq = 5043; + public static final int PullPrivateChatRsp = 4994; + public static final int PullRecentChatReq = 4995; + public static final int PullRecentChatRsp = 5025; + public static final int PushTipsAllDataNotify = 2226; + public static final int PushTipsChangeNotify = 2262; + public static final int PushTipsReadFinishReq = 2289; + public static final int PushTipsReadFinishRsp = 2209; + public static final int QueryCodexMonsterBeKilledNumReq = 4207; + public static final int QueryCodexMonsterBeKilledNumRsp = 4208; + public static final int QueryPathReq = 2360; + public static final int QueryPathRsp = 2333; + public static final int QuestCreateEntityReq = 434; + public static final int QuestCreateEntityRsp = 479; + public static final int QuestDelNotify = 410; + public static final int QuestDestroyEntityReq = 491; + public static final int QuestDestroyEntityRsp = 497; + public static final int QuestDestroyNpcReq = 426; + public static final int QuestDestroyNpcRsp = 462; + public static final int QuestGlobalVarNotify = 466; + public static final int QuestListNotify = 460; + public static final int QuestListUpdateNotify = 433; + public static final int QuestProgressUpdateNotify = 446; + public static final int QuestTransmitReq = 473; + public static final int QuestTransmitRsp = 463; + public static final int QuestUpdateQuestVarNotify = 483; + public static final int QuestUpdateQuestVarReq = 474; + public static final int QuestUpdateQuestVarRsp = 456; + public static final int QuickUseWidgetReq = 4276; + public static final int QuickUseWidgetRsp = 4265; + public static final int ReadMailNotify = 1410; + public static final int ReadPrivateChatReq = 4984; + public static final int ReadPrivateChatRsp = 5029; + public static final int ReceivedTrialAvatarActivityRewardReq = 2020; + public static final int ReceivedTrialAvatarActivityRewardRsp = 2087; + public static final int RechargeReq = 4135; + public static final int RechargeRsp = 4125; + public static final int RedeemLegendaryKeyReq = 481; + public static final int RedeemLegendaryKeyRsp = 427; + public static final int RefreshBackgroundAvatarReq = 1744; + public static final int RefreshBackgroundAvatarRsp = 1719; + public static final int RegionSearchChangeRegionNotify = 5625; + public static final int RegionSearchNotify = 5635; + public static final int ReliquaryPromoteReq = 665; + public static final int ReliquaryPromoteRsp = 603; + public static final int ReliquaryUpgradeReq = 689; + public static final int ReliquaryUpgradeRsp = 609; + public static final int RemoveBlacklistReq = 4080; + public static final int RemoveBlacklistRsp = 4064; + public static final int RemoveRandTaskInfoNotify = 132; + public static final int ReportTrackingIOInfoNotify = 4117; + public static final int ResinCardDataUpdateNotify = 4126; + public static final int ResinChangeNotify = 623; + public static final int ReunionActivateNotify = 5081; + public static final int ReunionBriefInfoReq = 5085; + public static final int ReunionBriefInfoRsp = 5075; + public static final int ReunionDailyRefreshNotify = 5072; + public static final int ReunionPrivilegeChangeNotify = 5100; + public static final int ReunionSettleNotify = 5096; + public static final int RobotPushPlayerDataNotify = 88; + public static final int SalesmanDeliverItemReq = 2103; + public static final int SalesmanDeliverItemRsp = 2198; + public static final int SalesmanTakeRewardReq = 2091; + public static final int SalesmanTakeRewardRsp = 2171; + public static final int SalesmanTakeSpecialRewardReq = 2156; + public static final int SalesmanTakeSpecialRewardRsp = 2102; + public static final int SaveCoopDialogReq = 1972; + public static final int SaveCoopDialogRsp = 1952; + public static final int SaveMainCoopReq = 1995; + public static final int SaveMainCoopRsp = 1998; + public static final int SceneAreaUnlockNotify = 209; + public static final int SceneAreaWeatherNotify = 213; + public static final int SceneAudioNotify = 3260; + public static final int SceneAvatarStaminaStepReq = 234; + public static final int SceneAvatarStaminaStepRsp = 279; + public static final int SceneCreateEntityReq = 267; + public static final int SceneCreateEntityRsp = 220; + public static final int SceneDataNotify = 3179; + public static final int SceneDestroyEntityReq = 280; + public static final int SceneDestroyEntityRsp = 264; + public static final int SceneEntitiesMoveCombineNotify = 3312; + public static final int SceneEntitiesMovesReq = 221; + public static final int SceneEntitiesMovesRsp = 207; + public static final int SceneEntityAppearNotify = 293; + public static final int SceneEntityDisappearNotify = 244; + public static final int SceneEntityDrownReq = 265; + public static final int SceneEntityDrownRsp = 203; + public static final int SceneEntityMoveNotify = 291; + public static final int SceneEntityMoveReq = 245; + public static final int SceneEntityMoveRsp = 275; + public static final int SceneForceLockNotify = 266; + public static final int SceneForceUnlockNotify = 201; + public static final int SceneGalleryInfoNotify = 5528; + public static final int SceneInitFinishReq = 215; + public static final int SceneInitFinishRsp = 237; + public static final int SceneKickPlayerNotify = 259; + public static final int SceneKickPlayerReq = 204; + public static final int SceneKickPlayerRsp = 206; + public static final int ScenePlayBattleInfoListNotify = 4378; + public static final int ScenePlayBattleInfoNotify = 4410; + public static final int ScenePlayBattleInterruptNotify = 4441; + public static final int ScenePlayBattleResultNotify = 4447; + public static final int ScenePlayBattleUidOpNotify = 4438; + public static final int ScenePlayGuestReplyInviteReq = 4394; + public static final int ScenePlayGuestReplyInviteRsp = 4395; + public static final int ScenePlayGuestReplyNotify = 4425; + public static final int ScenePlayInfoListNotify = 4429; + public static final int ScenePlayInviteResultNotify = 4384; + public static final int ScenePlayOutofRegionNotify = 4405; + public static final int ScenePlayOwnerCheckReq = 4383; + public static final int ScenePlayOwnerCheckRsp = 4360; + public static final int ScenePlayOwnerInviteNotify = 4443; + public static final int ScenePlayOwnerStartInviteReq = 4365; + public static final int ScenePlayOwnerStartInviteRsp = 4387; + public static final int ScenePlayerInfoNotify = 236; + public static final int ScenePlayerLocationNotify = 297; + public static final int ScenePlayerSoundNotify = 243; + public static final int ScenePointUnlockNotify = 274; + public static final int SceneRouteChangeNotify = 270; + public static final int SceneTeamUpdateNotify = 1696; + public static final int SceneTimeNotify = 229; + public static final int SceneTransToPointReq = 256; + public static final int SceneTransToPointRsp = 283; + public static final int SceneWeatherForcastReq = 3167; + public static final int SceneWeatherForcastRsp = 3023; + public static final int SeaLampCoinNotify = 2028; + public static final int SeaLampContributeItemReq = 2122; + public static final int SeaLampContributeItemRsp = 2084; + public static final int SeaLampFlyLampNotify = 2075; + public static final int SeaLampFlyLampReq = 2174; + public static final int SeaLampFlyLampRsp = 2080; + public static final int SeaLampPopularityNotify = 2062; + public static final int SeaLampTakeContributionRewardReq = 2052; + public static final int SeaLampTakeContributionRewardRsp = 2057; + public static final int SeaLampTakePhaseRewardReq = 2109; + public static final int SeaLampTakePhaseRewardRsp = 2132; + public static final int SealBattleBeginNotify = 225; + public static final int SealBattleEndNotify = 249; + public static final int SealBattleProgressNotify = 285; + public static final int SeeMonsterReq = 299; + public static final int SeeMonsterRsp = 300; + public static final int SelectAsterMidDifficultyReq = 2019; + public static final int SelectAsterMidDifficultyRsp = 2003; + public static final int SelectEffigyChallengeConditionReq = 2143; + public static final int SelectEffigyChallengeConditionRsp = 2072; + public static final int SelectWorktopOptionReq = 837; + public static final int SelectWorktopOptionRsp = 893; + public static final int ServerAnnounceNotify = 2199; + public static final int ServerAnnounceRevokeNotify = 2129; + public static final int ServerBuffChangeNotify = 332; + public static final int ServerCondMeetQuestListUpdateNotify = 401; + public static final int ServerDisconnectClientNotify = 186; + public static final int ServerGlobalValueChangeNotify = 1188; + public static final int ServerLogNotify = 79; + public static final int ServerTimeNotify = 34; + public static final int ServerUpdateGlobalValueNotify = 1197; + public static final int SetBattlePassViewedReq = 2637; + public static final int SetBattlePassViewedRsp = 2606; + public static final int SetCoopChapterViewedReq = 1980; + public static final int SetCoopChapterViewedRsp = 1988; + public static final int SetCurExpeditionChallengeIdReq = 2017; + public static final int SetCurExpeditionChallengeIdRsp = 2099; + public static final int SetEntityClientDataNotify = 3303; + public static final int SetEquipLockStateReq = 635; + public static final int SetEquipLockStateRsp = 657; + public static final int SetFriendEnterHomeOptionReq = 4613; + public static final int SetFriendEnterHomeOptionRsp = 4724; + public static final int SetFriendRemarkNameReq = 4023; + public static final int SetFriendRemarkNameRsp = 4013; + public static final int SetNameCardReq = 4089; + public static final int SetNameCardRsp = 4009; + public static final int SetOpenStateReq = 162; + public static final int SetOpenStateRsp = 189; + public static final int SetPlayerBirthdayReq = 4097; + public static final int SetPlayerBirthdayRsp = 4088; + public static final int SetPlayerBornDataReq = 155; + public static final int SetPlayerBornDataRsp = 146; + public static final int SetPlayerHeadImageReq = 4046; + public static final int SetPlayerHeadImageRsp = 4074; + public static final int SetPlayerNameReq = 183; + public static final int SetPlayerNameRsp = 126; + public static final int SetPlayerPropReq = 188; + public static final int SetPlayerPropRsp = 128; + public static final int SetPlayerSignatureReq = 4028; + public static final int SetPlayerSignatureRsp = 4055; + public static final int SetSceneWeatherAreaReq = 271; + public static final int SetSceneWeatherAreaRsp = 205; + public static final int SetUpAvatarTeamReq = 1671; + public static final int SetUpAvatarTeamRsp = 1634; + public static final int SetUpLunchBoxWidgetReq = 4286; + public static final int SetUpLunchBoxWidgetRsp = 4293; + public static final int SetWidgetSlotReq = 4266; + public static final int SetWidgetSlotRsp = 4279; + public static final int ShowCommonTipsNotify = 3277; + public static final int ShowMessageNotify = 15; + public static final int ShowTemplateReminderNotify = 3164; + public static final int SignInInfoReq = 2510; + public static final int SignInInfoRsp = 2515; + public static final int SocialDataNotify = 4063; + public static final int SpringUseReq = 1720; + public static final int SpringUseRsp = 1727; + public static final int StartArenaChallengeLevelReq = 2022; + public static final int StartArenaChallengeLevelRsp = 2033; + public static final int StartCoopPointReq = 1956; + public static final int StartCoopPointRsp = 1962; + public static final int StartEffigyChallengeReq = 2123; + public static final int StartEffigyChallengeRsp = 2166; + public static final int StoreItemChangeNotify = 610; + public static final int StoreItemDelNotify = 615; + public static final int StoreWeightLimitNotify = 633; + public static final int SyncScenePlayTeamEntityNotify = 3296; + public static final int SyncTeamEntityNotify = 338; + public static final int TakeAchievementGoalRewardReq = 2695; + public static final int TakeAchievementGoalRewardRsp = 2698; + public static final int TakeAchievementRewardReq = 2685; + public static final int TakeAchievementRewardRsp = 2675; + public static final int TakeAsterSpecialRewardReq = 2051; + public static final int TakeAsterSpecialRewardRsp = 2041; + public static final int TakeBattlePassMissionPointReq = 2617; + public static final int TakeBattlePassMissionPointRsp = 2636; + public static final int TakeBattlePassRewardReq = 2613; + public static final int TakeBattlePassRewardRsp = 2601; + public static final int TakeCityReputationExploreRewardReq = 2888; + public static final int TakeCityReputationExploreRewardRsp = 2828; + public static final int TakeCityReputationLevelRewardReq = 2810; + public static final int TakeCityReputationLevelRewardRsp = 2815; + public static final int TakeCityReputationParentQuestReq = 2893; + public static final int TakeCityReputationParentQuestRsp = 2844; + public static final int TakeCompoundOutputReq = 108; + public static final int TakeCompoundOutputRsp = 117; + public static final int TakeCoopRewardReq = 1996; + public static final int TakeCoopRewardRsp = 1981; + public static final int TakeDeliveryDailyRewardReq = 2055; + public static final int TakeDeliveryDailyRewardRsp = 2104; + public static final int TakeEffigyFirstPassRewardReq = 2071; + public static final int TakeEffigyFirstPassRewardRsp = 2034; + public static final int TakeEffigyRewardReq = 2113; + public static final int TakeEffigyRewardRsp = 2008; + public static final int TakeFirstShareRewardReq = 4008; + public static final int TakeFirstShareRewardRsp = 4017; + public static final int TakeFurnitureMakeReq = 4751; + public static final int TakeFurnitureMakeRsp = 4457; + public static final int TakeHuntingOfferReq = 4750; + public static final int TakeHuntingOfferRsp = 4782; + public static final int TakeInvestigationRewardReq = 1926; + public static final int TakeInvestigationRewardRsp = 1925; + public static final int TakeInvestigationTargetRewardReq = 1915; + public static final int TakeInvestigationTargetRewardRsp = 1929; + public static final int TakeMaterialDeleteReturnReq = 651; + public static final int TakeMaterialDeleteReturnRsp = 684; + public static final int TakeOfferingLevelRewardReq = 2921; + public static final int TakeOfferingLevelRewardRsp = 2910; + public static final int TakePlayerLevelRewardReq = 151; + public static final int TakePlayerLevelRewardRsp = 184; + public static final int TakeRegionSearchRewardReq = 5645; + public static final int TakeRegionSearchRewardRsp = 5648; + public static final int TakeResinCardDailyRewardReq = 4136; + public static final int TakeResinCardDailyRewardRsp = 4143; + public static final int TakeReunionFirstGiftRewardReq = 5095; + public static final int TakeReunionFirstGiftRewardRsp = 5098; + public static final int TakeReunionMissionRewardReq = 5056; + public static final int TakeReunionMissionRewardRsp = 5062; + public static final int TakeReunionSignInRewardReq = 5067; + public static final int TakeReunionSignInRewardRsp = 5086; + public static final int TakeReunionWatcherRewardReq = 5065; + public static final int TakeReunionWatcherRewardRsp = 5077; + public static final int TakeoffEquipReq = 655; + public static final int TakeoffEquipRsp = 646; + public static final int TaskVarNotify = 178; + public static final int TeamResonanceChangeNotify = 1046; + public static final int TowerAllDataReq = 2445; + public static final int TowerAllDataRsp = 2475; + public static final int TowerBriefDataNotify = 2460; + public static final int TowerBuffSelectReq = 2497; + public static final int TowerBuffSelectRsp = 2488; + public static final int TowerCurLevelRecordChangeNotify = 2410; + public static final int TowerDailyRewardProgressChangeNotify = 2415; + public static final int TowerEnterLevelReq = 2479; + public static final int TowerEnterLevelRsp = 2491; + public static final int TowerFloorRecordChangeNotify = 2433; + public static final int TowerGetFloorStarRewardReq = 2489; + public static final int TowerGetFloorStarRewardRsp = 2409; + public static final int TowerLevelEndNotify = 2464; + public static final int TowerLevelStarCondNotify = 2401; + public static final int TowerMiddleLevelChangeTeamNotify = 2466; + public static final int TowerRecordHandbookReq = 2473; + public static final int TowerRecordHandbookRsp = 2463; + public static final int TowerSurrenderReq = 2426; + public static final int TowerSurrenderRsp = 2462; + public static final int TowerTeamSelectReq = 2493; + public static final int TowerTeamSelectRsp = 2444; + public static final int TreasureMapBonusChallengeNotify = 2121; + public static final int TreasureMapCurrencyNotify = 2127; + public static final int TreasureMapDetectorDataNotify = 4272; + public static final int TreasureMapGuideTaskDoneNotify = 2200; + public static final int TreasureMapMpChallengeNotify = 2177; + public static final int TreasureMapRegionActiveNotify = 2141; + public static final int TreasureMapRegionInfoNotify = 2120; + public static final int TrialAvatarFirstPassDungeonNotify = 2093; + public static final int TrialAvatarInDungeonIndexNotify = 2138; + public static final int TriggerCreateGadgetToEquipPartNotify = 373; + public static final int TryEnterHomeReq = 4622; + public static final int TryEnterHomeRsp = 4731; + public static final int UnfreezeGroupLimitNotify = 3401; + public static final int UnionCmdNotify = 55; + public static final int UnlockAvatarTalentReq = 1060; + public static final int UnlockAvatarTalentRsp = 1033; + public static final int UnlockCoopChapterReq = 1965; + public static final int UnlockCoopChapterRsp = 1977; + public static final int UnlockNameCardNotify = 4001; + public static final int UnlockPersonalLineReq = 402; + public static final int UnlockPersonalLineRsp = 452; + public static final int UnlockTransPointReq = 3421; + public static final int UnlockTransPointRsp = 3073; + public static final int UnlockedFurnitureFormulaDataNotify = 4700; + public static final int UnlockedFurnitureSuiteDataNotify = 4788; + public static final int UnmarkEntityInMinMapNotify = 247; + public static final int UpdateAbilityCreatedMovingPlatformNotify = 828; + public static final int UpdatePS4BlockListReq = 4081; + public static final int UpdatePS4BlockListRsp = 4027; + public static final int UpdatePS4FriendListNotify = 4056; + public static final int UpdatePlayerShowAvatarListReq = 4036; + public static final int UpdatePlayerShowAvatarListRsp = 4024; + public static final int UpdatePlayerShowNameCardListReq = 4030; + public static final int UpdatePlayerShowNameCardListRsp = 4047; + public static final int UpdateReunionWatcherNotify = 5087; + public static final int UseItemReq = 645; + public static final int UseItemRsp = 675; + public static final int UseMiracleRingReq = 5235; + public static final int UseMiracleRingRsp = 5225; + public static final int UseWidgetCreateGadgetReq = 4278; + public static final int UseWidgetCreateGadgetRsp = 4290; + public static final int UseWidgetRetractGadgetReq = 4255; + public static final int UseWidgetRetractGadgetRsp = 4297; + public static final int ViewCodexReq = 4210; + public static final int ViewCodexRsp = 4209; + public static final int WatcherAllDataNotify = 2260; + public static final int WatcherChangeNotify = 2233; + public static final int WatcherEventNotify = 2210; + public static final int WatcherEventTypeNotify = 2215; + public static final int WaterSpritePhaseFinishNotify = 2097; + public static final int WeaponAwakenReq = 664; + public static final int WeaponAwakenRsp = 601; + public static final int WeaponPromoteReq = 626; + public static final int WeaponPromoteRsp = 662; + public static final int WeaponUpgradeReq = 656; + public static final int WeaponUpgradeRsp = 683; + public static final int WearEquipReq = 688; + public static final int WearEquipRsp = 628; + public static final int WidgetCoolDownNotify = 4277; + public static final int WidgetGadgetAllDataNotify = 4260; + public static final int WidgetGadgetDataNotify = 4268; + public static final int WidgetGadgetDestroyNotify = 4282; + public static final int WidgetReportReq = 4287; + public static final int WidgetReportRsp = 4256; + public static final int WidgetSlotChangeNotify = 4299; + public static final int WindSeedClientNotify = 1134; + public static final int WorktopOptionNotify = 815; + public static final int WorldAllRoutineTypeNotify = 3525; + public static final int WorldDataNotify = 3330; + public static final int WorldOwnerBlossomBriefInfoNotify = 2715; + public static final int WorldOwnerBlossomScheduleInfoNotify = 2737; + public static final int WorldOwnerDailyTaskNotify = 130; + public static final int WorldPlayerDieNotify = 211; + public static final int WorldPlayerInfoNotify = 3088; + public static final int WorldPlayerLocationNotify = 224; + public static final int WorldPlayerRTTNotify = 26; + public static final int WorldPlayerReviveReq = 216; + public static final int WorldPlayerReviveRsp = 222; + public static final int WorldRoutineChangeNotify = 3548; + public static final int WorldRoutineTypeCloseNotify = 3513; + public static final int WorldRoutineTypeRefreshNotify = 3545; + +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtil.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtil.java new file mode 100644 index 00000000..6395a7d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtil.java @@ -0,0 +1,44 @@ +package emu.grasscutter.net.packet; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class PacketOpcodesUtil { + private static Int2ObjectMap opcodeMap; + + static { + opcodeMap = new Int2ObjectOpenHashMap(); + + Field[] fields = PacketOpcodes.class.getFields(); + + for (Field f : fields) { + try { + opcodeMap.put(f.getInt(null), f.getName()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static String getOpcodeName(int opcode) { + if (opcode <= 0) return "UNKNOWN"; + return opcodeMap.getOrDefault(opcode, "UNKNOWN"); + } + + public static void dumpOpcodes() { + try { + BufferedWriter out = new BufferedWriter(new FileWriter("opcodes.ini")); + for (Int2ObjectMap.Entry entry : opcodeMap.int2ObjectEntrySet()) { + out.write(String.format("%04X=%s%s", entry.getIntKey(), entry.getValue(), System.lineSeparator())); + } + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/emu/grasscutter/net/packet/PacketWriter.java b/src/main/java/emu/grasscutter/net/packet/PacketWriter.java new file mode 100644 index 00000000..c8ea039b --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/PacketWriter.java @@ -0,0 +1,177 @@ +package emu.grasscutter.net.packet; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class PacketWriter { + // Little endian + private final ByteArrayOutputStream baos; + + public PacketWriter() { + this.baos = new ByteArrayOutputStream(128); + } + + public byte[] build() { + return baos.toByteArray(); + } + + // Writers + + public void writeEmpty(int i) { + while (i > 0) { + baos.write(0); + i--; + } + } + + public void writeMax(int i) { + while (i > 0) { + baos.write(0xFF); + i--; + } + } + + public void writeInt8(byte b) { + baos.write(b); + } + + public void writeInt8(int i) { + baos.write((byte) i); + } + + public void writeBoolean(boolean b) { + baos.write(b ? 1 : 0); + } + + public void writeUint8(byte b) { + // Unsigned byte + baos.write(b & 0xFF); + } + + public void writeUint8(int i) { + + baos.write((byte) i & 0xFF); + } + + public void writeUint16(int i) { + // Unsigned short + baos.write((byte) (i & 0xFF)); + baos.write((byte) ((i >>> 8) & 0xFF)); + } + + public void writeUint24(int i) { + // 24 bit integer + baos.write((byte) (i & 0xFF)); + baos.write((byte) ((i >>> 8) & 0xFF)); + baos.write((byte) ((i >>> 16) & 0xFF)); + } + + public void writeInt16(int i) { + // Signed short + baos.write((byte) i); + baos.write((byte) (i >>> 8)); + } + + public void writeUint32(int i) { + // Unsigned int + baos.write((byte) (i & 0xFF)); + baos.write((byte) ((i >>> 8) & 0xFF)); + baos.write((byte) ((i >>> 16) & 0xFF)); + baos.write((byte) ((i >>> 24) & 0xFF)); + } + + public void writeInt32(int i) { + // Signed int + baos.write((byte) i); + baos.write((byte) (i >>> 8)); + baos.write((byte) (i >>> 16)); + baos.write((byte) (i >>> 24)); + } + + public void writeUint32(long i) { + // Unsigned int (long) + baos.write((byte) (i & 0xFF)); + baos.write((byte) ((i >>> 8) & 0xFF)); + baos.write((byte) ((i >>> 16) & 0xFF)); + baos.write((byte) ((i >>> 24) & 0xFF)); + } + + public void writeFloat(float f){ + this.writeUint32(Float.floatToRawIntBits(f)); + } + + public void writeUint64(long l) { + baos.write((byte) (l & 0xFF)); + baos.write((byte) ((l >>> 8) & 0xFF)); + baos.write((byte) ((l >>> 16) & 0xFF)); + baos.write((byte) ((l >>> 24) & 0xFF)); + baos.write((byte) ((l >>> 32) & 0xFF)); + baos.write((byte) ((l >>> 40) & 0xFF)); + baos.write((byte) ((l >>> 48) & 0xFF)); + baos.write((byte) ((l >>> 56) & 0xFF)); + } + + public void writeDouble(double d){ + long l = Double.doubleToLongBits(d); + this.writeUint64(l); + } + + public void writeString16(String s) { + if (s == null) { + this.writeUint16(0); + return; + } + + this.writeUint16(s.length() * 2); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + this.writeUint16((short) c); + } + } + + public void writeString8(String s) { + if (s == null) { + this.writeUint16(0); + return; + } + + this.writeUint16(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + this.writeUint8((byte) c); + } + } + + public void writeDirectString8(String s, int expectedSize) { + if (s == null) { + return; + } + + for (int i = 0; i < expectedSize; i++) { + char c = i < s.length() ? s.charAt(i) : 0; + this.writeUint8((byte) c); + } + } + + public void writeBytes(byte[] bytes) { + try { + baos.write(bytes); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void writeBytes(int[] bytes) { + byte[] b = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) + b[i] = (byte)bytes[i]; + + try { + baos.write(b); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/emu/grasscutter/net/packet/Retcode.java b/src/main/java/emu/grasscutter/net/packet/Retcode.java new file mode 100644 index 00000000..c061a189 --- /dev/null +++ b/src/main/java/emu/grasscutter/net/packet/Retcode.java @@ -0,0 +1,6 @@ +package emu.grasscutter.net.packet; + +public class Retcode { + public static final int SUCCESS = 0; + public static final int FAIL = 1; +} diff --git a/src/main/java/emu/grasscutter/netty/MihoyoKcpChannel.java b/src/main/java/emu/grasscutter/netty/MihoyoKcpChannel.java new file mode 100644 index 00000000..23357919 --- /dev/null +++ b/src/main/java/emu/grasscutter/netty/MihoyoKcpChannel.java @@ -0,0 +1,89 @@ +package emu.grasscutter.netty; + +import emu.grasscutter.Grasscutter; +import io.jpower.kcp.netty.UkcpChannel; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +public abstract class MihoyoKcpChannel extends ChannelInboundHandlerAdapter { + private UkcpChannel kcpChannel; + private ChannelHandlerContext ctx; + private boolean isActive; + + public UkcpChannel getChannel() { + return kcpChannel; + } + + public boolean isActive() { + return this.isActive; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + this.kcpChannel = (UkcpChannel) ctx.channel(); + this.ctx = ctx; + this.isActive = true; + + this.onConnect(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + this.isActive = false; + + this.onDisconnect(); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ByteBuf data = (ByteBuf) msg; + onMessage(ctx, data); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + close(); + } + + protected void send(byte[] data) { + if (!isActive()) { + return; + } + ByteBuf packet = Unpooled.wrappedBuffer(data); + kcpChannel.writeAndFlush(packet); + } + + public void close() { + if (getChannel() != null) { + getChannel().close(); + } + } + + /* + protected void logPacket(ByteBuffer buf) { + ByteBuf b = Unpooled.wrappedBuffer(buf.array()); + logPacket(b); + } + */ + + protected void logPacket(ByteBuf buf) { + Grasscutter.getLogger().info("Received: \n" + ByteBufUtil.prettyHexDump(buf)); + } + + // Events + + protected abstract void onConnect(); + + protected abstract void onDisconnect(); + + public abstract void onMessage(ChannelHandlerContext ctx, ByteBuf data); +} diff --git a/src/main/java/emu/grasscutter/netty/MihoyoKcpHandshaker.java b/src/main/java/emu/grasscutter/netty/MihoyoKcpHandshaker.java new file mode 100644 index 00000000..cf94c7c7 --- /dev/null +++ b/src/main/java/emu/grasscutter/netty/MihoyoKcpHandshaker.java @@ -0,0 +1,85 @@ +package emu.grasscutter.netty; + +import java.net.SocketAddress; +import java.nio.channels.SelectableChannel; +import java.util.List; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.nio.AbstractNioMessageChannel; + +public class MihoyoKcpHandshaker extends AbstractNioMessageChannel { + + protected MihoyoKcpHandshaker(Channel parent, SelectableChannel ch, int readInterestOp) { + super(parent, ch, readInterestOp); + } + + @Override + public ChannelConfig config() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isActive() { + // TODO Auto-generated method stub + return false; + } + + @Override + public ChannelMetadata metadata() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected int doReadMessages(List buf) throws Exception { + // TODO Auto-generated method stub + return 0; + } + + @Override + protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception { + // TODO Auto-generated method stub + return false; + } + + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + // TODO Auto-generated method stub + return false; + } + + @Override + protected void doFinishConnect() throws Exception { + // TODO Auto-generated method stub + + } + + @Override + protected SocketAddress localAddress0() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + // TODO Auto-generated method stub + + } + + @Override + protected void doDisconnect() throws Exception { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/emu/grasscutter/netty/MihoyoKcpServer.java b/src/main/java/emu/grasscutter/netty/MihoyoKcpServer.java new file mode 100644 index 00000000..c3a9297b --- /dev/null +++ b/src/main/java/emu/grasscutter/netty/MihoyoKcpServer.java @@ -0,0 +1,94 @@ +package emu.grasscutter.netty; + +import java.net.InetSocketAddress; + +import emu.grasscutter.Grasscutter; +import io.jpower.kcp.netty.ChannelOptionHelper; +import io.jpower.kcp.netty.UkcpChannelOption; +import io.jpower.kcp.netty.UkcpServerChannel; +import io.netty.bootstrap.UkcpServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; + +@SuppressWarnings("rawtypes") +public class MihoyoKcpServer extends Thread { + private EventLoopGroup group; + private UkcpServerBootstrap bootstrap; + + private ChannelInitializer serverInitializer; + private InetSocketAddress address; + + public MihoyoKcpServer(InetSocketAddress address) { + this.address = address; + this.setName("Netty Server Thread"); + } + + public InetSocketAddress getAddress() { + return this.address; + } + + public ChannelInitializer getServerInitializer() { + return serverInitializer; + } + + public void setServerInitializer(ChannelInitializer serverInitializer) { + this.serverInitializer = serverInitializer; + } + + @Override + public void run() { + if (getServerInitializer() == null) { + this.setServerInitializer(new MihoyoKcpServerInitializer()); + } + + try { + group = new NioEventLoopGroup(); + bootstrap = new UkcpServerBootstrap(); + bootstrap.group(group) + .channel(UkcpServerChannel.class) + .childHandler(this.getServerInitializer()); + ChannelOptionHelper + .nodelay(bootstrap, true, 20, 2, true) + .childOption(UkcpChannelOption.UKCP_MTU, 1200); + + // Start handler + this.onStart(); + + // Start the server. + ChannelFuture f = bootstrap.bind(getAddress()).sync(); + + // Start finish handler + this.onStartFinish(); + + // Wait until the server socket is closed. + f.channel().closeFuture().sync(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + // Close + finish(); + } + } + + public void onStart() { + + } + + public void onStartFinish() { + + } + + private void finish() { + try { + group.shutdownGracefully(); + } catch (Exception e) { + + } + Grasscutter.getLogger().info("Game Server closed"); + } +} + + diff --git a/src/main/java/emu/grasscutter/netty/MihoyoKcpServerInitializer.java b/src/main/java/emu/grasscutter/netty/MihoyoKcpServerInitializer.java new file mode 100644 index 00000000..7d4e5892 --- /dev/null +++ b/src/main/java/emu/grasscutter/netty/MihoyoKcpServerInitializer.java @@ -0,0 +1,15 @@ +package emu.grasscutter.netty; + +import io.jpower.kcp.netty.UkcpChannel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; + +@SuppressWarnings("unused") +public class MihoyoKcpServerInitializer extends ChannelInitializer { + + @Override + protected void initChannel(UkcpChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + } + +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java new file mode 100644 index 00000000..b4ef1322 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.dispatch; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +public class DispatchHttpJsonHandler implements HttpHandler { + private final String response; + + public DispatchHttpJsonHandler(String response) { + this.response = response; + } + + @Override + public void handle(HttpExchange t) throws IOException { + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java new file mode 100644 index 00000000..e33e1de2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -0,0 +1,467 @@ +package emu.grasscutter.server.dispatch; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URLDecoder; +import java.security.KeyStore; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.protobuf.ByteString; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; +import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp; +import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; +import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; +import emu.grasscutter.server.dispatch.json.ComboTokenReqJson; +import emu.grasscutter.server.dispatch.json.ComboTokenResJson; +import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; +import emu.grasscutter.server.dispatch.json.LoginResultJson; +import emu.grasscutter.server.dispatch.json.LoginTokenRequestJson; +import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; + +import com.sun.net.httpserver.HttpServer; + +public class DispatchServer { + private HttpsServer server; + private final InetSocketAddress address; + private final Gson gson; + private QueryCurrRegionHttpRsp currRegion; + + public String regionListBase64; + public String regionCurrentBase64; + + public static String query_region_list = ""; + public static String query_cur_region = ""; + + public DispatchServer() { + this.address = new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().DispatchServerPort); + this.gson = new GsonBuilder().create(); + + this.loadQueries(); + this.initRegion(); + } + + public InetSocketAddress getAddress() { + return address; + } + + public Gson getGsonFactory() { + return gson; + } + + public QueryCurrRegionHttpRsp getCurrRegion() { + return currRegion; + } + + public void loadQueries() { + File file; + + file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_region_list.txt"); + if (file.exists()) { + query_region_list = new String(FileUtils.read(file)); + } else { + Grasscutter.getLogger().warn("query_region_list not found! Using default region list."); + } + + file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + if (file.exists()) { + query_cur_region = new String(FileUtils.read(file)); + } else { + Grasscutter.getLogger().warn("query_cur_region not found! Using default current region."); + } + } + + private void initRegion() { + try { + byte[] decoded = Base64.getDecoder().decode(query_region_list); + QueryRegionListHttpRsp rl = QueryRegionListHttpRsp.parseFrom(decoded); + + byte[] decoded2 = Base64.getDecoder().decode(query_cur_region); + QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); + + RegionSimpleInfo server = RegionSimpleInfo.newBuilder() + .setName("os_usa") + .setTitle(Grasscutter.getConfig().GameServerName) + .setType("DEV_PUBLIC") + .setDispatchUrl("https://" + Grasscutter.getConfig().DispatchServerIp + ":" + getAddress().getPort() + "/query_cur_region") + .build(); + + RegionSimpleInfo serverTest2 = RegionSimpleInfo.newBuilder() + .setName("os_euro") + .setTitle("Grasscutter") + .setType("DEV_PUBLIC") + .setDispatchUrl("https://" + Grasscutter.getConfig().DispatchServerIp + ":" + getAddress().getPort() + "/query_cur_region") + .build(); + + QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder() + .addServers(server) + .addServers(serverTest2) + .setClientSecretKey(rl.getClientSecretKey()) + .setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted()) + .setEnableLoginPc(true) + .build(); + + RegionInfo currentRegion = regionQuery.getRegionInfo().toBuilder() + .setIp(Grasscutter.getConfig().GameServerIp) + .setPort(Grasscutter.getConfig().GameServerPort) + .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .build(); + + QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(currentRegion).build(); + + this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); + this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()); + this.currRegion = parsedRegionQuery; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void start() throws Exception { + server = HttpsServer.create(getAddress(), 0); + SSLContext sslContext = SSLContext.getInstance("TLS"); + + try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { + char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray(); + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(fis, keystorePassword); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, keystorePassword); + + sslContext.init(kmf.getKeyManagers(), null, null); + + server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + } catch (Exception e) { + Grasscutter.getLogger().error("No SSL cert found!"); + return; + } + + server.createContext("/", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + //Create a response form the request query parameters + String response = "Hello"; + //Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + //Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + + // Dispatch + server.createContext("/query_region_list", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + // Log + Grasscutter.getLogger().info("Client request: query_region_list"); + // Create a response form the request query parameters + String response = regionListBase64; + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + server.createContext("/query_cur_region", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + // Log + Grasscutter.getLogger().info("Client request: query_cur_region"); + // Create a response form the request query parameters + URI uri = t.getRequestURI(); + String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; + if (uri.getQuery() != null && uri.getQuery().length() > 0) { + response = regionCurrentBase64; + } + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + // Login via account + server.createContext("/hk4e_global/mdk/shield/api/login", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + // Get post data + LoginAccountRequestJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); + } catch (Exception e) { + + } + // Create response json + if (requestData == null) { + return; + } + LoginResultJson responseData = new LoginResultJson(); + + // Login + Account account = DatabaseHelper.getAccountByName(requestData.account); + + // Test + if (account == null) { + responseData.retcode = -201; + responseData.message = "Username not found."; + } else { + responseData.message = "OK"; + responseData.data.account.uid = account.getId(); + responseData.data.account.token = account.generateSessionKey(); + responseData.data.account.email = account.getEmail(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + // Login via token + server.createContext("/hk4e_global/mdk/shield/api/verify", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + // Get post data + LoginTokenRequestJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); + } catch (Exception e) { + + } + // Create response json + if (requestData == null) { + return; + } + LoginResultJson responseData = new LoginResultJson(); + + // Login + Account account = DatabaseHelper.getAccountById(requestData.uid); + + // Test + if (account == null || !account.getSessionKey().equals(requestData.token)) { + responseData.retcode = -111; + responseData.message = "Game account cache information error"; + } else { + responseData.message = "OK"; + responseData.data.account.uid = requestData.uid; + responseData.data.account.token = requestData.token; + responseData.data.account.email = account.getEmail(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + // Exchange for combo token + server.createContext("/hk4e_global/combo/granter/login/v2/login", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + // Get post data + ComboTokenReqJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); + } catch (Exception e) { + + } + // Create response json + if (requestData == null || requestData.data == null) { + return; + } + LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login data + ComboTokenResJson responseData = new ComboTokenResJson(); + + // Login + Account account = DatabaseHelper.getAccountById(loginData.uid); + + // Test + if (account == null || !account.getSessionKey().equals(loginData.token)) { + responseData.retcode = -201; + responseData.message = "Wrong session key."; + } else { + responseData.message = "OK"; + responseData.data.open_id = loginData.uid; + responseData.data.combo_id = "157795300"; + responseData.data.combo_token = account.generateLoginToken(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + // Agreement and Protocol + server.createContext( // hk4e-sdk-os.hoyoverse.com + "/hk4e_global/mdk/agreement/api/getAgreementInfos", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}") + ); + server.createContext( // hk4e-sdk-os.hoyoverse.com + "/hk4e_global/combo/granter/api/compareProtocolVersion", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}") + ); + // Game data + server.createContext( // hk4e-api-os.hoyoverse.com + "/common/hk4e_global/announcement/api/getAlertPic", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}") + ); + server.createContext( // hk4e-api-os.hoyoverse.com + "/common/hk4e_global/announcement/api/getAlertAnn", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}") + ); + server.createContext( // hk4e-api-os.hoyoverse.com + "/common/hk4e_global/announcement/api/getAnnList", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0,\"type_list\":[],\"alert\":false,\"alert_id\":0,\"timezone\":0,\"t\":\"" + System.currentTimeMillis() + "\"}}") + ); + server.createContext( // hk4e-api-os-static.hoyoverse.com + "/common/hk4e_global/announcement/api/getAnnContent", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0}}") + ); + server.createContext( // hk4e-sdk-os.hoyoverse.com + "/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}") + ); + // Captcha + server.createContext( // api-account-os.hoyoverse.com + "/account/risky/api/check", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"c8820f246a5241ab9973f71df3ddd791\",\"action\":\"\",\"geetest\":{\"challenge\":\"\",\"gt\":\"\",\"new_captcha\":0,\"success\":1}}}") + ); + // Config + server.createContext( // sdk-os-static.hoyoverse.com + "/combo/box/api/config/sdk/combo", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}") + ); + server.createContext( // hk4e-sdk-os-static.hoyoverse.com + "/hk4e_global/combo/granter/api/getConfig", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}") + ); + server.createContext( // hk4e-sdk-os-static.hoyoverse.com + "/hk4e_global/mdk/shield/api/loadConfig", + new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}") + ); + // Test api? + server.createContext( // abtest-api-data-sg.hoyoverse.com + "/data_abtest_api/config/experiment/list", + new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}") + ); + // Log Server + server.createContext( // log-upload-os.mihoyo.com + "/log/sdk/upload", + new DispatchHttpJsonHandler("{\"code\":0}") + ); + server.createContext( // log-upload-os.mihoyo.com + "/sdk/upload", + new DispatchHttpJsonHandler("{\"code\":0}") + ); + // Start server + server.start(); + Grasscutter.getLogger().info("Dispatch server started on port " + getAddress().getPort()); + + // Logging servers + HttpServer overseaLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 8888), 0); + overseaLogServer.createContext( // overseauspider.yuanshen.com + "/log", + new DispatchHttpJsonHandler("{\"code\":0}") + ); + overseaLogServer.start(); + Grasscutter.getLogger().info("Log server (overseauspider) started on port " + 8888); + + HttpServer uploadLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 80), 0); + uploadLogServer.createContext( // log-upload-os.mihoyo.com + "/crash/dataUpload", + new DispatchHttpJsonHandler("{\"code\":0}") + ); + uploadLogServer.createContext("/gacha", new HttpHandler() { + @Override + public void handle(HttpExchange t) throws IOException { + //Create a response form the request query parameters + String response = "Gacha"; + //Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + //Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + uploadLogServer.start(); + Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + 80); + } + + private Map parseQueryString(String qs) { + Map result = new HashMap<>(); + if (qs == null) + return result; + + int last = 0, next, l = qs.length(); + while (last < l) { + next = qs.indexOf('&', last); + if (next == -1) + next = l; + + if (next > last) { + int eqPos = qs.indexOf('=', last); + try { + if (eqPos < 0 || eqPos > next) + result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); + else + result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java + } + } + last = next + 1; + } + return result; + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java new file mode 100644 index 00000000..dac26cfa --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.dispatch.json; + +public class ComboTokenReqJson { + public int app_id; + public int channel_id; + public String data; + public String device; + public String sign; + + public class LoginTokenData { + public String uid; + public String token; + public boolean guest; + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java new file mode 100644 index 00000000..731d5085 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.dispatch.json; + +public class ComboTokenResJson { + public String message; + public int retcode; + public LoginData data = new LoginData(); + + public class LoginData { + public int account_type = 1; + public boolean heartbeat; + public String combo_id; + public String combo_token; + public String open_id; + public String data = "{\"guest\":false}"; + public String fatigue_remind = null; // ? + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java new file mode 100644 index 00000000..cb3aff34 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java @@ -0,0 +1,7 @@ +package emu.grasscutter.server.dispatch.json; + +public class LoginAccountRequestJson { + public String account; + public String password; + public boolean is_crypto; +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java new file mode 100644 index 00000000..5988752d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.dispatch.json; + +public class LoginResultJson { + public String message; + public int retcode; + public VerifyData data = new VerifyData(); + + public class VerifyData { + public VerifyAccountData account = new VerifyAccountData(); + public boolean device_grant_required = false; + public String realname_operation = "NONE"; + public boolean realperson_required = false; + public boolean safe_mobile_required = false; + } + + public class VerifyAccountData { + public String uid; + public String name = ""; + public String email; + public String mobile = ""; + public String is_email_verify = "0"; + public String realname = ""; + public String identity_card = ""; + public String token; + public String safe_mobile = ""; + public String facebook_name = ""; + public String twitter_name = ""; + public String game_center_name = ""; + public String google_name = ""; + public String apple_name = ""; + public String sony_name = ""; + public String tap_name = ""; + public String country = "US"; + public String reactivate_ticket = ""; + public String area_code = "**"; + public String device_grant_ticket = ""; + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java new file mode 100644 index 00000000..12fed8f0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java @@ -0,0 +1,6 @@ +package emu.grasscutter.server.dispatch.json; + +public class LoginTokenRequestJson { + public String uid; + public String token; +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java new file mode 100644 index 00000000..1baf3253 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -0,0 +1,160 @@ +package emu.grasscutter.server.game; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.dungeons.DungeonManager; +import emu.grasscutter.game.gacha.GachaManager; +import emu.grasscutter.game.managers.ChatManager; +import emu.grasscutter.game.managers.InventoryManager; +import emu.grasscutter.game.managers.MultiplayerManager; +import emu.grasscutter.game.shop.ShopManager; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; +import emu.grasscutter.netty.MihoyoKcpServer; + +public class GameServer extends MihoyoKcpServer { + private final InetSocketAddress address; + private final GameServerPacketHandler packetHandler; + private final Timer gameLoop; + + private final Map players; + + private final ChatManager chatManager; + private final InventoryManager inventoryManager; + private final GachaManager gachaManager; + private final ShopManager shopManager; + private final MultiplayerManager multiplayerManager; + private final DungeonManager dungeonManager; + + public GameServer(InetSocketAddress address) { + super(address); + this.setServerInitializer(new GameServerInitializer(this)); + this.address = address; + this.packetHandler = new GameServerPacketHandler(PacketHandler.class); + this.players = new ConcurrentHashMap<>(); + + this.chatManager = new ChatManager(this); + this.inventoryManager = new InventoryManager(this); + this.gachaManager = new GachaManager(this); + this.shopManager = new ShopManager(this); + this.multiplayerManager = new MultiplayerManager(this); + this.dungeonManager = new DungeonManager(this); + + // Ticker + this.gameLoop = new Timer(); + this.gameLoop.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + onTick(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }, new Date(), 1000L); + + // Shutdown hook + Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); + } + + public GameServerPacketHandler getPacketHandler() { + return packetHandler; + } + + public Map getPlayers() { + return players; + } + + public ChatManager getChatManager() { + return chatManager; + } + + public InventoryManager getInventoryManager() { + return inventoryManager; + } + + public GachaManager getGachaManager() { + return gachaManager; + } + + public ShopManager getShopManager() { + return shopManager; + } + + public MultiplayerManager getMultiplayerManager() { + return multiplayerManager; + } + + public DungeonManager getDungeonManager() { + return dungeonManager; + } + + public void registerPlayer(GenshinPlayer player) { + getPlayers().put(player.getId(), player); + } + + public GenshinPlayer getPlayerById(int id) { + return this.getPlayers().get(id); + } + + public GenshinPlayer forceGetPlayerById(int id) { + // Console check + if (id == GenshinConstants.SERVER_CONSOLE_UID) { + return null; + } + + // Get from online players + GenshinPlayer player = this.getPlayerById(id); + + // Check database if character isnt here + if (player == null) { + player = DatabaseHelper.getPlayerById(id); + } + + return player; + } + + public SocialDetail.Builder getSocialDetailById(int id) { + // Get from online players + GenshinPlayer player = this.forceGetPlayerById(id); + + if (player == null) { + return null; + } + + return player.getSocialDetail(); + } + + public void onTick() throws Exception { + for (GenshinPlayer player : this.getPlayers().values()) { + player.onTick(); + } + } + + @Override + public void onStartFinish() { + Grasscutter.getLogger().info("Game Server started on port " + address.getPort()); + } + + public void onServerShutdown() { + // Kick and save all players + List list = new ArrayList<>(this.getPlayers().size()); + list.addAll(this.getPlayers().values()); + + for (GenshinPlayer player : list) { + player.getSession().close(); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java b/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java new file mode 100644 index 00000000..5472c1db --- /dev/null +++ b/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.game; + +import emu.grasscutter.netty.MihoyoKcpServerInitializer; +import io.jpower.kcp.netty.UkcpChannel; +import io.netty.channel.ChannelPipeline; + +public class GameServerInitializer extends MihoyoKcpServerInitializer { + private GameServer server; + + public GameServerInitializer(GameServer server) { + this.server = server; + } + + @Override + protected void initChannel(UkcpChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + GameSession session = new GameSession(server); + pipeline.addLast(session); + } +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java new file mode 100644 index 00000000..11863766 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java @@ -0,0 +1,94 @@ +package emu.grasscutter.server.game; + +import java.util.Set; + +import org.reflections.Reflections; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.server.game.GameSession.SessionState; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class GameServerPacketHandler { + private final Int2ObjectMap handlers; + + public GameServerPacketHandler(Class handlerClass) { + this.handlers = new Int2ObjectOpenHashMap<>(); + + this.registerHandlers(handlerClass); + } + + public void registerHandlers(Class handlerClass) { + Reflections reflections = new Reflections("emu.grasscutter.server.packet"); + Set handlerClasses = reflections.getSubTypesOf(handlerClass); + + for (Object obj : handlerClasses) { + Class c = (Class) obj; + + try { + Opcodes opcode = c.getAnnotation(Opcodes.class); + + if (opcode == null || opcode.disabled() || opcode.value() <= 0) { + continue; + } + + PacketHandler packetHandler = (PacketHandler) c.newInstance(); + + this.handlers.put(opcode.value(), packetHandler); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Debug + Grasscutter.getLogger().info("Registered " + this.handlers.size() + " " + handlerClass.getSimpleName() + "s"); + } + + public void handle(GameSession session, int opcode, byte[] header, byte[] payload) { + PacketHandler handler = null; + + handler = this.handlers.get(opcode); + + if (handler != null) { + try { + // Make sure session is ready for packets + SessionState state = session.getState(); + + if (opcode == PacketOpcodes.PingReq) { + // Always continue if packet is ping request + } else if (opcode == PacketOpcodes.GetPlayerTokenReq) { + if (state != SessionState.WAITING_FOR_TOKEN) { + return; + } + } else if (opcode == PacketOpcodes.PlayerLoginReq) { + if (state != SessionState.WAITING_FOR_LOGIN) { + return; + } + } else if (opcode == PacketOpcodes.SetPlayerBornDataReq) { + if (state != SessionState.PICKING_CHARACTER) { + return; + } + } else { + if (state != SessionState.ACTIVE) { + return; + } + } + + // Handle + handler.handle(session, header, payload); + } catch (Exception ex) { + // TODO Remove this when no more needed + ex.printStackTrace(); + } + return; // Packet successfully handled + } + + // Log unhandled packets + if (Grasscutter.getConfig().LOG_PACKETS) { + //Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode)); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java new file mode 100644 index 00000000..29dcd46b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -0,0 +1,250 @@ +package emu.grasscutter.server.game; + +import java.io.File; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodesUtil; +import emu.grasscutter.netty.MihoyoKcpChannel; +import emu.grasscutter.utils.Crypto; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; + +public class GameSession extends MihoyoKcpChannel { + private GameServer server; + + private Account account; + private GenshinPlayer player; + + private boolean useSecretKey; + private SessionState state; + + private int clientTime; + private long lastPingTime; + private int lastClientSeq = 10; + + public GameSession(GameServer server) { + this.server = server; + this.state = SessionState.WAITING_FOR_TOKEN; + this.lastPingTime = System.currentTimeMillis(); + } + + public GameServer getServer() { + return server; + } + + public InetSocketAddress getAddress() { + if (this.getChannel() == null) { + return null; + } + return this.getChannel().remoteAddress(); + } + + public boolean useSecretKey() { + return useSecretKey; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public String getAccountId() { + return this.getAccount().getId(); + } + + public GenshinPlayer getPlayer() { + return player; + } + + public synchronized void setPlayer(GenshinPlayer player) { + this.player = player; + this.player.setSession(this); + this.player.setAccount(this.getAccount()); + } + + public SessionState getState() { + return state; + } + + public void setState(SessionState state) { + this.state = state; + } + + public boolean isLoggedIn() { + return this.getPlayer() != null; + } + + public void setUseSecretKey(boolean useSecretKey) { + this.useSecretKey = useSecretKey; + } + + public int getClientTime() { + return this.clientTime; + } + + public long getLastPingTime() { + return lastPingTime; + } + + public void updateLastPingTime(int clientTime) { + this.clientTime = clientTime; + this.lastPingTime = System.currentTimeMillis(); + } + + public int getNextClientSequence() { + return ++lastClientSeq; + } + + @Override + protected void onConnect() { + Grasscutter.getLogger().info("Client connected from " + getAddress().getHostString().toLowerCase()); + } + + @Override + protected synchronized void onDisconnect() { // Synchronize so we dont add character at the same time + Grasscutter.getLogger().info("Client disconnected from " + getAddress().getHostString().toLowerCase()); + + // Set state so no more packets can be handled + this.setState(SessionState.INACTIVE); + + // Save after disconnecting + if (this.isLoggedIn()) { + // Save + getPlayer().onLogout(); + // Remove from gameserver + getServer().getPlayers().remove(getPlayer().getId()); + } + } + + protected void logPacket(ByteBuffer buf) { + ByteBuf b = Unpooled.wrappedBuffer(buf.array()); + logPacket(b); + } + + public void replayPacket(int opcode, String name) { + String filePath = Grasscutter.getConfig().PACKETS_FOLDER + name; + File p = new File(filePath); + + if (!p.exists()) return; + + byte[] packet = FileUtils.read(p); + + GenshinPacket genshinPacket = new GenshinPacket(opcode); + genshinPacket.setData(packet); + + // Log + logPacket(genshinPacket.getOpcode()); + + send(genshinPacket); + } + + public void send(GenshinPacket genshinPacket) { + // Test + if (genshinPacket.getOpcode() <= 0) { + Grasscutter.getLogger().warn("Tried to send packet with missing cmd id!"); + return; + } + + // Header + if (genshinPacket.shouldBuildHeader()) { + genshinPacket.buildHeader(this.getNextClientSequence()); + } + + // Build packet + byte[] data = genshinPacket.build(); + + // Log + if (Grasscutter.getConfig().LOG_PACKETS) { + logPacket(genshinPacket); + } + + // Send + send(data); + } + + private void logPacket(int opcode) { + //Grasscutter.getLogger().info("SEND: " + PacketOpcodesUtil.getOpcodeName(opcode)); + //System.out.println(Utils.bytesToHex(genshinPacket.getData())); + } + + private void logPacket(GenshinPacket genshinPacket) { + Grasscutter.getLogger().info("SEND: " + PacketOpcodesUtil.getOpcodeName(genshinPacket.getOpcode()) + " (" + genshinPacket.getOpcode() + ")"); + System.out.println(Utils.bytesToHex(genshinPacket.getData())); + } + + @Override + public void onMessage(ChannelHandlerContext ctx, ByteBuf data) { + // Decrypt and turn back into a packet + byte[] byteData = Utils.byteBufToArray(data); + Crypto.xor(byteData, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY); + ByteBuf packet = Unpooled.wrappedBuffer(byteData); + + // Log + //logPacket(packet); + + // Handle + try { + while (packet.readableBytes() > 0) { + // Length + if (packet.readableBytes() < 12) { + return; + } + + // Packet sanity check + int const1 = packet.readShort(); + if (const1 != 17767) { + return; // Bad packet + } + + // Data + int opcode = packet.readShort(); + int headerLength = packet.readShort(); + int payloadLength = packet.readInt(); + + byte[] header = new byte[headerLength]; + byte[] payload = new byte[payloadLength]; + + packet.readBytes(header); + packet.readBytes(payload); + + // Sanity check #2 + int const2 = packet.readShort(); + if (const2 != -30293) { + return; // Bad packet + } + + // Log packet + if (Grasscutter.getConfig().LOG_PACKETS) { + Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")"); + System.out.println(Utils.bytesToHex(payload)); + } + + // Handle + getServer().getPacketHandler().handle(this, opcode, header, payload); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + packet.release(); + } + } + + public enum SessionState { + INACTIVE, + WAITING_FOR_TOKEN, + WAITING_FOR_LOGIN, + PICKING_CHARACTER, + ACTIVE; + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/Handler.java b/src/main/java/emu/grasscutter/server/packet/recv/Handler.java new file mode 100644 index 00000000..e56664ac --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/Handler.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.NONE) +public class Handler extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java new file mode 100644 index 00000000..710ea0fe --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilityInvocationsNotifyOuterClass.AbilityInvocationsNotify; +import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AbilityInvocationsNotify) +public class HandlerAbilityInvocationsNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AbilityInvocationsNotify notif = AbilityInvocationsNotify.parseFrom(payload); + + for (AbilityInvokeEntry entry : notif.getInvokesList()) { + //System.out.println(entry.getArgumentType() + ": " + Utils.bytesToHex(entry.getAbilityData().toByteArray())); + session.getPlayer().getAbilityInvokeHandler().addEntry(entry.getForwardType(), entry); + } + + if (notif.getInvokesList().size() > 0) { + session.getPlayer().getAbilityInvokeHandler().update(session.getPlayer()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAskAddFriendReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAskAddFriendReq.java new file mode 100644 index 00000000..3449b88d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAskAddFriendReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AskAddFriendReqOuterClass.AskAddFriendReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AskAddFriendReq) +public class HandlerAskAddFriendReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AskAddFriendReq req = AskAddFriendReq.parseFrom(payload); + + session.getPlayer().getFriendsList().sendFriendRequest(req.getTargetUid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeCostumeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeCostumeReq.java new file mode 100644 index 00000000..d2270a52 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeCostumeReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarChangeCostumeReqOuterClass.AvatarChangeCostumeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeRsp; + +@Opcodes(PacketOpcodes.AvatarChangeCostumeReq) +public class HandlerAvatarChangeCostumeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarChangeCostumeReq req = AvatarChangeCostumeReq.parseFrom(payload); + + boolean success = session.getPlayer().getAvatars().changeCostume(req.getAvatarGuid(), req.getCostumeId()); + + if (success) { + session.getPlayer().sendPacket(new PacketAvatarChangeCostumeRsp(req.getAvatarGuid(), req.getCostumeId())); + } else { + session.getPlayer().sendPacket(new PacketAvatarChangeCostumeRsp()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarDieAnimationEndReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarDieAnimationEndReq.java new file mode 100644 index 00000000..2935da00 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarDieAnimationEndReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarDieAnimationEndReqOuterClass.AvatarDieAnimationEndReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AvatarDieAnimationEndReq) +public class HandlerAvatarDieAnimationEndReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarDieAnimationEndReq req = AvatarDieAnimationEndReq.parseFrom(payload); + + session.getPlayer().getTeamManager().onAvatarDie(req.getDieGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarPromoteReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarPromoteReq.java new file mode 100644 index 00000000..cd863f5b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarPromoteReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarPromoteReqOuterClass.AvatarPromoteReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AvatarPromoteReq) +public class HandlerAvatarPromoteReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarPromoteReq req = AvatarPromoteReq.parseFrom(payload); + + // Ascend avatar + session.getServer().getInventoryManager().promoteAvatar(session.getPlayer(), req.getGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarSkillUpgradeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarSkillUpgradeReq.java new file mode 100644 index 00000000..e179bcd8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarSkillUpgradeReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarSkillUpgradeReqOuterClass.AvatarSkillUpgradeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AvatarSkillUpgradeReq) +public class HandlerAvatarSkillUpgradeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarSkillUpgradeReq req = AvatarSkillUpgradeReq.parseFrom(payload); + + // Level up avatar talent + session.getServer().getInventoryManager().upgradeAvatarSkill(session.getPlayer(), req.getAvatarGuid(), req.getAvatarSkillId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarUpgradeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarUpgradeReq.java new file mode 100644 index 00000000..3254b232 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarUpgradeReq.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarUpgradeReqOuterClass.AvatarUpgradeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.AvatarUpgradeReq) +public class HandlerAvatarUpgradeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarUpgradeReq req = AvatarUpgradeReq.parseFrom(payload); + + // Level up avatar + session.getServer().getInventoryManager().upgradeAvatar( + session.getPlayer(), + req.getAvatarGuid(), + req.getItemId(), + req.getCount() + ); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarWearFlycloakReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarWearFlycloakReq.java new file mode 100644 index 00000000..a33db86d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarWearFlycloakReq.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarWearFlycloakReqOuterClass.AvatarWearFlycloakReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarWearFlycloakRsp; + +@Opcodes(PacketOpcodes.AvatarWearFlycloakReq) +public class HandlerAvatarWearFlycloakReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarWearFlycloakReq req = AvatarWearFlycloakReq.parseFrom(payload); + + boolean success = session.getPlayer().getAvatars().wearFlycloak(req.getAvatarGuid(), req.getFlycloakId()); + + if (success) { + session.getPlayer().sendPacket(new PacketAvatarWearFlycloakRsp(req.getAvatarGuid(), req.getFlycloakId())); + } else { + session.getPlayer().sendPacket(new PacketAvatarWearFlycloakRsp()); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCalcWeaponUpgradeReturnItemsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCalcWeaponUpgradeReturnItemsReq.java new file mode 100644 index 00000000..79f199ee --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCalcWeaponUpgradeReturnItemsReq.java @@ -0,0 +1,34 @@ +package emu.grasscutter.server.packet.recv; + +import java.util.List; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CalcWeaponUpgradeReturnItemsReqOuterClass.CalcWeaponUpgradeReturnItemsReq; +import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketCalcWeaponUpgradeReturnItemsRsp; + +@Opcodes(PacketOpcodes.CalcWeaponUpgradeReturnItemsReq) +public class HandlerCalcWeaponUpgradeReturnItemsReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + CalcWeaponUpgradeReturnItemsReq req = CalcWeaponUpgradeReturnItemsReq.parseFrom(payload); + + List returnOres = session.getServer().getInventoryManager().calcWeaponUpgradeReturnItems( + session.getPlayer(), + req.getTargetWeaponGuid(), + req.getFoodWeaponGuidListList(), + req.getItemParamListList() + ); + + if (returnOres != null) { + session.send(new PacketCalcWeaponUpgradeReturnItemsRsp(req.getTargetWeaponGuid(), returnOres)); + } else { + session.send(new PacketCalcWeaponUpgradeReturnItemsRsp()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeAvatarReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeAvatarReq.java new file mode 100644 index 00000000..eeba7441 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeAvatarReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeAvatarReqOuterClass.ChangeAvatarReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ChangeAvatarReq) +public class HandlerChangeAvatarReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ChangeAvatarReq req = ChangeAvatarReq.parseFrom(payload); + + session.getPlayer().getTeamManager().changeAvatar(req.getGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java new file mode 100644 index 00000000..42f633c2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeGameTimeReqOuterClass.ChangeGameTimeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketChangeGameTimeRsp; + +@Opcodes(PacketOpcodes.ChangeGameTimeReq) +public class HandlerChangeGameTimeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ChangeGameTimeReq req = ChangeGameTimeReq.parseFrom(payload); + + session.getPlayer().getWorld().changeTime(req.getGameTime()); + session.getPlayer().sendPacket(new PacketChangeGameTimeRsp(session.getPlayer().getWorld())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeMpTeamAvatarReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeMpTeamAvatarReq.java new file mode 100644 index 00000000..a8b0b05e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeMpTeamAvatarReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeMpTeamAvatarReqOuterClass.ChangeMpTeamAvatarReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ChangeMpTeamAvatarReq) +public class HandlerChangeMpTeamAvatarReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ChangeMpTeamAvatarReq req = ChangeMpTeamAvatarReq.parseFrom(payload); + + session.getPlayer().getTeamManager().setupMpTeam(req.getAvatarGuidListList()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeTeamNameReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeTeamNameReq.java new file mode 100644 index 00000000..c754ff7c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeTeamNameReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeTeamNameReqOuterClass.ChangeTeamNameReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ChangeTeamNameReq) +public class HandlerChangeTeamNameReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ChangeTeamNameReq req = ChangeTeamNameReq.parseFrom(payload); + + session.getPlayer().getTeamManager().setTeamName(req.getTeamId(), req.getTeamName()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChooseCurAvatarTeamReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChooseCurAvatarTeamReq.java new file mode 100644 index 00000000..16c28ad4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChooseCurAvatarTeamReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChooseCurAvatarTeamReqOuterClass.ChooseCurAvatarTeamReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ChooseCurAvatarTeamReq) +public class HandlerChooseCurAvatarTeamReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ChooseCurAvatarTeamReq req = ChooseCurAvatarTeamReq.parseFrom(payload); + + session.getPlayer().getTeamManager().setCurrentTeam(req.getTeamId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java new file mode 100644 index 00000000..58ae9691 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -0,0 +1,51 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; +import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.CombatInvocationsNotify) +public class HandlerCombatInvocationsNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); + + for (CombatInvokeEntry entry : notif.getInvokeListList()) { + switch (entry.getArgumentType()) { + case CombatEvtBeingHit: + // Handle damage + EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); + session.getPlayer().getWorld().handleAttack(hitInfo.getAttackResult()); + break; + case EntityMove: + // Handle movement + EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); + GenshinEntity entity = session.getPlayer().getWorld().getEntityById(moveInfo.getEntityId()); + if (entity != null) { + entity.getPosition().set(moveInfo.getMotionInfo().getPos()); + entity.getRotation().set(moveInfo.getMotionInfo().getRot()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + entity.setMotionState(moveInfo.getMotionInfo().getState()); + } + break; + default: + break; + } + + session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); + } + + if (notif.getInvokeListList().size() > 0) { + session.getPlayer().getCombatInvokeHandler().update(session.getPlayer()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDealAddFriendReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDealAddFriendReq.java new file mode 100644 index 00000000..ebf1e39d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDealAddFriendReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DealAddFriendReqOuterClass.DealAddFriendReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.DealAddFriendReq) +public class HandlerDealAddFriendReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DealAddFriendReq req = DealAddFriendReq.parseFrom(payload); + + session.getPlayer().getFriendsList().handleFriendRequest(req.getTargetUid(), req.getDealAddFriendResult()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDeleteFriendReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDeleteFriendReq.java new file mode 100644 index 00000000..5f0c958b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDeleteFriendReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DeleteFriendReqOuterClass.DeleteFriendReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.DeleteFriendReq) +public class HandlerDeleteFriendReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DeleteFriendReq req = DeleteFriendReq.parseFrom(payload); + + session.getPlayer().getFriendsList().deleteFriend(req.getTargetUid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDestroyMaterialReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDestroyMaterialReq.java new file mode 100644 index 00000000..e39239f6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDestroyMaterialReq.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DestroyMaterialReqOuterClass.DestroyMaterialReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.DestroyMaterialReq) +public class HandlerDestroyMaterialReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DestroyMaterialReq req = DestroyMaterialReq.parseFrom(payload); + + // Delete items + session.getServer().getInventoryManager().destroyMaterial(session.getPlayer(), req.getMaterialListList()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDoGachaReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDoGachaReq.java new file mode 100644 index 00000000..0aa680a1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDoGachaReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DoGachaReqOuterClass.DoGachaReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.DoGachaReq) +public class HandlerDoGachaReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DoGachaReq req = DoGachaReq.parseFrom(payload); + + session.getServer().getGachaManager().doPulls(session.getPlayer(), req.getGachaType(), req.getGachaTimes()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java new file mode 100644 index 00000000..a490e9d7 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.DungeonEntryInfoReq) +public class HandlerDungeonEntryInfoReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java new file mode 100644 index 00000000..7a929f13 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.GenshinPlayer.SceneLoadState; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEnterSceneDoneRsp; +import emu.grasscutter.server.packet.send.PacketPlayerTimeNotify; +import emu.grasscutter.server.packet.send.PacketScenePlayerLocationNotify; +import emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify; +import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify; + +@Opcodes(PacketOpcodes.EnterSceneDoneReq) +public class HandlerEnterSceneDoneReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Finished loading + session.getPlayer().setSceneLoadState(SceneLoadState.LOADED); + + // Done + session.send(new PacketEnterSceneDoneRsp(session.getPlayer())); + session.send(new PacketPlayerTimeNotify(session.getPlayer())); // Probably not the right place + + // Spawn player in world + session.getPlayer().getWorld().spawnPlayer(session.getPlayer()); + + // Spawn other entites already in world + session.getPlayer().getWorld().showOtherEntities(session.getPlayer()); + + // Locations + session.send(new PacketWorldPlayerLocationNotify(session.getPlayer().getWorld())); + session.send(new PacketScenePlayerLocationNotify(session.getPlayer())); + session.send(new PacketWorldPlayerRTTNotify(session.getPlayer().getWorld())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneReadyReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneReadyReq.java new file mode 100644 index 00000000..b9a979b6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneReadyReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEnterScenePeerNotify; +import emu.grasscutter.server.packet.send.PacketEnterSceneReadyRsp; + +@Opcodes(PacketOpcodes.EnterSceneReadyReq) +public class HandlerEnterSceneReadyReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) { + session.send(new PacketEnterScenePeerNotify(session.getPlayer())); + session.send(new PacketEnterSceneReadyRsp(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterWorldAreaReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterWorldAreaReq.java new file mode 100644 index 00000000..53211a12 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterWorldAreaReq.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterWorldAreaReqOuterClass.EnterWorldAreaReq; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEnterWorldAreaRsp; + +@Opcodes(PacketOpcodes.EnterWorldAreaReq) +public class HandlerEnterWorldAreaReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PacketHead head = PacketHead.parseFrom(header); + EnterWorldAreaReq enterWorld = EnterWorldAreaReq.parseFrom(payload); + + session.send(new PacketEnterWorldAreaRsp(head.getClientSequenceId(), enterWorld)); + //session.send(new PacketScenePlayerLocationNotify(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEntityAiSyncNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEntityAiSyncNotify.java new file mode 100644 index 00000000..23c3b3ae --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEntityAiSyncNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EntityAiSyncNotifyOuterClass.EntityAiSyncNotify; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEntityAiSyncNotify; + +@Opcodes(PacketOpcodes.EntityAiSyncNotify) +public class HandlerEntityAiSyncNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EntityAiSyncNotify notify = EntityAiSyncNotify.parseFrom(payload); + + if (notify.getLocalAvatarAlertedMonsterListCount() > 0) { + session.getPlayer().getWorld().broadcastPacket(new PacketEntityAiSyncNotify(notify)); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncCombatThreatInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncCombatThreatInfoNotify.java new file mode 100644 index 00000000..84c6df4d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncCombatThreatInfoNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtAiSyncCombatThreatInfoNotify) +public class HandlerEvtAiSyncCombatThreatInfoNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncSkillCdNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncSkillCdNotify.java new file mode 100644 index 00000000..03ad8cad --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAiSyncSkillCdNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtAiSyncSkillCdNotify) +public class HandlerEvtAiSyncSkillCdNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java new file mode 100644 index 00000000..f4c5708d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityClientGadget; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtCreateGadgetNotify) +public class HandlerEvtCreateGadgetNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtCreateGadgetNotify notify = EvtCreateGadgetNotify.parseFrom(payload); + + // Dont handle in singleplayer + if (!session.getPlayer().getWorld().isMultiplayer()) { + return; + } + + // Create entity and summon in world + EntityClientGadget gadget = new EntityClientGadget(session.getPlayer().getWorld(), session.getPlayer(), notify); + session.getPlayer().getWorld().onPlayerCreateGadget(gadget); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java new file mode 100644 index 00000000..d391860f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtDestroyGadgetNotifyOuterClass.EvtDestroyGadgetNotify; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtDestroyGadgetNotify) +public class HandlerEvtDestroyGadgetNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtDestroyGadgetNotify notify = EvtDestroyGadgetNotify.parseFrom(payload); + + // Dont handle in singleplayer + if (!session.getPlayer().getWorld().isMultiplayer()) { + return; + } + + session.getPlayer().getWorld().onPlayerDestroyGadget(notify.getEntityId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java new file mode 100644 index 00000000..6c8b6515 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.GadgetInteractReq) +public class HandlerGadgetInteractReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); + + session.getPlayer().interactWith(req.getGadgetEntityId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetActivityInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetActivityInfoReq.java new file mode 100644 index 00000000..bf2cf749 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetActivityInfoReq.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetActivityInfoRsp; + +@Opcodes(PacketOpcodes.GetActivityInfoReq) +public class HandlerGetActivityInfoReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketGetActivityInfoRsp()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllH5ActivityInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllH5ActivityInfoReq.java new file mode 100644 index 00000000..9ec01d19 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllH5ActivityInfoReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketH5ActivityIdsNotify; + +@Opcodes(PacketOpcodes.GetAllH5ActivityInfoReq) +public class HandlerGetAllH5ActivityInfoReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketH5ActivityIdsNotify()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllUnlockNameCardReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllUnlockNameCardReq.java new file mode 100644 index 00000000..37bf3a03 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllUnlockNameCardReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetAllUnlockNameCardRsp; + +@Opcodes(PacketOpcodes.GetAllUnlockNameCardReq) +public class HandlerGetAllUnlockNameCardReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketGetAllUnlockNameCardRsp(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAuthkeyReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAuthkeyReq.java new file mode 100644 index 00000000..16440426 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAuthkeyReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetAuthkeyRsp; + +@Opcodes(PacketOpcodes.GetAuthkeyReq) +public class HandlerGetAuthkeyReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketGetAuthkeyRsp()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java new file mode 100644 index 00000000..6c4c703a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetGachaInfoRsp; + +@Opcodes(PacketOpcodes.GetGachaInfoReq) +public class HandlerGetGachaInfoReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerBlacklistReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerBlacklistReq.java new file mode 100644 index 00000000..2d0b650d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerBlacklistReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.GetPlayerBlacklistReq) +public class HandlerGetPlayerBlacklistReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new GenshinPacket(PacketOpcodes.GetPlayerBlacklistRsp).buildHeader(3)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerFriendListReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerFriendListReq.java new file mode 100644 index 00000000..a825f157 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerFriendListReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetPlayerFriendListRsp; + +@Opcodes(PacketOpcodes.GetPlayerFriendListReq) +public class HandlerGetPlayerFriendListReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + //session.send(new PacketGetPlayerAskFriendListRsp(session.getPlayer())); + session.send(new PacketGetPlayerFriendListRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerSocialDetailReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerSocialDetailReq.java new file mode 100644 index 00000000..8391f235 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerSocialDetailReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetPlayerSocialDetailReqOuterClass.GetPlayerSocialDetailReq; +import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetPlayerSocialDetailRsp; + +@Opcodes(PacketOpcodes.GetPlayerSocialDetailReq) +public class HandlerGetPlayerSocialDetailReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GetPlayerSocialDetailReq req = GetPlayerSocialDetailReq.parseFrom(payload); + + SocialDetail.Builder detail = session.getServer().getSocialDetailById(req.getUid()); + + if (detail != null) { + detail.setIsFriend(session.getPlayer().getFriendsList().isFriendsWith(req.getUid())); + } + + session.send(new PacketGetPlayerSocialDetailRsp(detail)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java new file mode 100644 index 00000000..dc686f5c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java @@ -0,0 +1,56 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.game.GameSession.SessionState; +import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp; + +@Opcodes(PacketOpcodes.GetPlayerTokenReq) +public class HandlerGetPlayerTokenReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GetPlayerTokenReq req = GetPlayerTokenReq.parseFrom(payload); + + // Authenticate + Account account = DatabaseHelper.getAccountById(req.getAccountUid()); + if (account == null) { + return; + } + + // Check token + if (!account.getToken().equals(req.getAccountToken())) { + return; + } + + // Set account + session.setAccount(account); + session.setUseSecretKey(true); + session.setState(SessionState.WAITING_FOR_LOGIN); + + // Has character + boolean doesPlayerExist = false; + if (account.getPlayerId() > 0) { + // Set flag for player existing + doesPlayerExist = DatabaseHelper.checkPlayerExists(account.getPlayerId()); + } + + // Set reserve player id if account doesnt exist + if (!doesPlayerExist) { + int id = DatabaseHelper.getNextPlayerId(session.getAccount().getPlayerId()); + if (id != session.getAccount().getPlayerId()) { + session.getAccount().setPlayerId(id); + session.getAccount().save(); + } + } + + // Send packet + session.send(new PacketGetPlayerTokenRsp(session, doesPlayerExist)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetRegionSearchReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetRegionSearchReq.java new file mode 100644 index 00000000..88ed2091 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetRegionSearchReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.GetRegionSearchReq) +public class HandlerGetRegionSearchReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetSceneAreaReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetSceneAreaReq.java new file mode 100644 index 00000000..ac890623 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetSceneAreaReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetSceneAreaReqOuterClass.GetSceneAreaReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetSceneAreaRsp; + +@Opcodes(PacketOpcodes.GetSceneAreaReq) +public class HandlerGetSceneAreaReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GetSceneAreaReq req = GetSceneAreaReq.parseFrom(payload); + + session.send(new PacketGetSceneAreaRsp(req.getSceneId())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetScenePointReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetScenePointReq.java new file mode 100644 index 00000000..f4352ba1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetScenePointReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetScenePointReqOuterClass.GetScenePointReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetScenePointRsp; + +@Opcodes(PacketOpcodes.GetScenePointReq) +public class HandlerGetScenePointReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GetScenePointReq req = GetScenePointReq.parseFrom(payload); + + session.send(new PacketGetScenePointRsp(req.getSceneId())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopReq.java new file mode 100644 index 00000000..9b7c6e96 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetShopReqOuterClass.GetShopReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetShopRsp; + +@Opcodes(PacketOpcodes.GetShopReq) +public class HandlerGetShopReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GetShopReq req = GetShopReq.parseFrom(payload); + + // TODO + session.send(new PacketGetShopRsp(req.getShopType())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopmallDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopmallDataReq.java new file mode 100644 index 00000000..c9c98f04 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetShopmallDataReq.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetShopmallDataRsp; + +@Opcodes(PacketOpcodes.GetShopmallDataReq) +public class HandlerGetShopmallDataReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // TODO add the correct shops + session.send(new PacketGetShopmallDataRsp()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java new file mode 100644 index 00000000..b41a6cc1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.GetWidgetSlotReq) +public class HandlerGetWidgetSlotReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Unhandled + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWorldMpInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWorldMpInfoReq.java new file mode 100644 index 00000000..e93ecfad --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWorldMpInfoReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetWorldMpInfoRsp; + +@Opcodes(PacketOpcodes.GetWorldMpInfoReq) +public class HandlerGetWorldMpInfoReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketGetWorldMpInfoRsp(session.getPlayer().getWorld())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java new file mode 100644 index 00000000..3a651e4c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; +import emu.grasscutter.net.proto.OperationOuterClass.Operation; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; + +@Opcodes(PacketOpcodes.MarkMapReq) +public class HandlerMarkMapReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + MarkMapReq req = MarkMapReq.parseFrom(payload); + + if (req.getOp() != Operation.Add) { + return; + } + + session.getPlayer().getPos().setX(req.getMark().getPos().getX()); + session.getPlayer().getPos().setZ(req.getMark().getPos().getZ()); + session.getPlayer().getPos().setY(300); + + Grasscutter.getLogger().info("Player [" + session.getPlayer().getId() + ":" + session.getPlayer().getNickname() + "] tp to " + session.getPlayer().getPos()); + + session.getPlayer().getWorld().broadcastPacket(new PacketSceneEntityAppearNotify(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAIConfigHashNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAIConfigHashNotify.java new file mode 100644 index 00000000..52149d46 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMonsterAIConfigHashNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.MonsterAIConfigHashNotify) +public class HandlerMonsterAIConfigHashNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerObstacleModifyNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerObstacleModifyNotify.java new file mode 100644 index 00000000..6c2bff83 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerObstacleModifyNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ObstacleModifyNotify) +public class HandlerObstacleModifyNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPathfindingEnterSceneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPathfindingEnterSceneReq.java new file mode 100644 index 00000000..2d69d07f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPathfindingEnterSceneReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPathfindingEnterSceneRsp; + +@Opcodes(PacketOpcodes.PathfindingEnterSceneReq) +public class HandlerPathfindingEnterSceneReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PacketHead head = PacketHead.parseFrom(header); + session.send(new PacketPathfindingEnterSceneRsp(head.getClientSequenceId())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPingReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPingReq.java new file mode 100644 index 00000000..70d661eb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPingReq.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.net.proto.PingReqOuterClass.PingReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPingRsp; + +@Opcodes(PacketOpcodes.PingReq) +public class HandlerPingReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PacketHead head = PacketHead.parseFrom(header); + PingReq ping = PingReq.parseFrom(payload); + + session.updateLastPingTime(ping.getClientTime()); + + session.send(new PacketPingRsp(head.getClientSequenceId(), ping.getClientTime())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpReq.java new file mode 100644 index 00000000..ed5591a1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpReq.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpReqOuterClass.PlayerApplyEnterMpReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpRsp; + +@Opcodes(PacketOpcodes.PlayerApplyEnterMpReq) +public class HandlerPlayerApplyEnterMpReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PlayerApplyEnterMpReq req = PlayerApplyEnterMpReq.parseFrom(payload); + + session.getServer().getMultiplayerManager().applyEnterMp(session.getPlayer(), req.getTargetUid()); + session.send(new PacketPlayerApplyEnterMpRsp(req.getTargetUid())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpResultReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpResultReq.java new file mode 100644 index 00000000..7a9c41db --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerApplyEnterMpResultReq.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpResultReqOuterClass.PlayerApplyEnterMpResultReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultRsp; + +@Opcodes(PacketOpcodes.PlayerApplyEnterMpResultReq) +public class HandlerPlayerApplyEnterMpResultReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PlayerApplyEnterMpResultReq req = PlayerApplyEnterMpResultReq.parseFrom(payload); + + session.getServer().getMultiplayerManager().applyEnterMpReply(session.getPlayer(), req.getApplyUid(), req.getIsAgreed()); + session.send(new PacketPlayerApplyEnterMpResultRsp(req.getApplyUid(), req.getIsAgreed())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java new file mode 100644 index 00000000..9a807e99 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; +import emu.grasscutter.net.proto.PlayerChatReqOuterClass.PlayerChatReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerChatRsp; + +@Opcodes(PacketOpcodes.PlayerChatReq) +public class HandlerPlayerChatReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PlayerChatReq req = PlayerChatReq.parseFrom(payload); + ChatInfo.ContentCase content = req.getChatInfo().getContentCase(); + + if (content == ChatInfo.ContentCase.TEXT) { + session.getServer().getChatManager().sendTeamChat(session.getPlayer(), req.getChannelId(), req.getChatInfo().getText()); + } else if (content == ChatInfo.ContentCase.ICON) { + session.getServer().getChatManager().sendTeamChat(session.getPlayer(), req.getChannelId(), req.getChatInfo().getIcon()); + } + + session.send(new PacketPlayerChatRsp()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java new file mode 100644 index 00000000..1fe4c8d5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java @@ -0,0 +1,14 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerForceExitReq) +public class HandlerPlayerForceExitReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Client should auto disconnect right now + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerGetForceQuitBanInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerGetForceQuitBanInfoReq.java new file mode 100644 index 00000000..b7afdb90 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerGetForceQuitBanInfoReq.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerGetForceQuitBanInfoRsp; + +@Opcodes(PacketOpcodes.PlayerGetForceQuitBanInfoReq) +public class HandlerPlayerGetForceQuitBanInfoReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + + if (session.getServer().getMultiplayerManager().leaveCoop(session.getPlayer())) { + // Success + session.send(new PacketPlayerGetForceQuitBanInfoRsp(0)); + } else { + // Fail + session.send(new PacketPlayerGetForceQuitBanInfoRsp(1)); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java new file mode 100644 index 00000000..183ef746 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java @@ -0,0 +1,52 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerLoginReqOuterClass.PlayerLoginReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.game.GameSession.SessionState; +import emu.grasscutter.server.packet.send.PacketPlayerLoginRsp; + +@Opcodes(PacketOpcodes.PlayerLoginReq) // Sends initial data packets +public class HandlerPlayerLoginReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Check + if (session.getAccount() == null) { + return; + } + + // Parse request + PlayerLoginReq req = PlayerLoginReq.parseFrom(payload); + + // Authenticate session + if (!req.getToken().equals(session.getAccount().getToken())) { + return; + } + + // Load character from db + GenshinPlayer player = DatabaseHelper.getPlayerById(session.getAccount().getPlayerId()); + + if (player == null) { + // Send packets + session.setState(SessionState.PICKING_CHARACTER); + session.send(new GenshinPacket(PacketOpcodes.DoSetPlayerBornDataNotify)); + } else { + // Set character + session.setPlayer(player); + + // Login done + session.getPlayer().onLogin(); + session.setState(SessionState.ACTIVE); + } + + // Final packet to tell client logging in is done + session.send(new PacketPlayerLoginRsp(session)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerSetPauseReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerSetPauseReq.java new file mode 100644 index 00000000..7c1680d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerSetPauseReq.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.net.proto.PlayerSetPauseReqOuterClass.PlayerSetPauseReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerSetPauseRsp; + +@Opcodes(PacketOpcodes.PlayerSetPauseReq) +public class HandlerPlayerSetPauseReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PacketHead head = PacketHead.parseFrom(header); + PlayerSetPauseReq req = PlayerSetPauseReq.parseFrom(payload); + + session.send(new PacketPlayerSetPauseRsp(head.getClientSequenceId())); + session.getPlayer().setPaused(req.getIsPaused()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java new file mode 100644 index 00000000..9c74946b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp; + +@Opcodes(PacketOpcodes.PostEnterSceneReq) +public class HandlerPostEnterSceneReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketPostEnterSceneRsp(session.getPlayer())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java new file mode 100644 index 00000000..081c0883 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PrivateChatReqOuterClass.PrivateChatReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PrivateChatReq) +public class HandlerPrivateChatReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PrivateChatReq req = PrivateChatReq.parseFrom(payload); + PrivateChatReq.ContentCase content = req.getContentCase(); + + if (content == PrivateChatReq.ContentCase.TEXT) { + session.getServer().getChatManager().sendPrivChat(session.getPlayer(), req.getTargetUid(), req.getText()); + } else if (content == PrivateChatReq.ContentCase.ICON) { + session.getServer().getChatManager().sendPrivChat(session.getPlayer(), req.getTargetUid(), req.getIcon()); + } + + //session.send(new GenshinPacket(PacketOpcodes.PrivateChatRsp)); // Unused by server + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullPrivateChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullPrivateChatReq.java new file mode 100644 index 00000000..b00e7ad1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullPrivateChatReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PullPrivateChatReqOuterClass.PullPrivateChatReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp; + +@Opcodes(PacketOpcodes.PullPrivateChatReq) +public class HandlerPullPrivateChatReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PullPrivateChatReq req = PullPrivateChatReq.parseFrom(payload); + + session.send(new PacketPullPrivateChatRsp()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullRecentChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullRecentChatReq.java new file mode 100644 index 00000000..3a7d8262 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPullRecentChatReq.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp; + +@Opcodes(PacketOpcodes.PullRecentChatReq) +public class HandlerPullRecentChatReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketPullRecentChatRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java new file mode 100644 index 00000000..aa8492b2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.QueryPathReq) +public class HandlerQueryPathReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerReliquaryUpgradeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerReliquaryUpgradeReq.java new file mode 100644 index 00000000..bd8ec9e5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerReliquaryUpgradeReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ReliquaryUpgradeReqOuterClass.ReliquaryUpgradeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ReliquaryUpgradeReq) +public class HandlerReliquaryUpgradeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ReliquaryUpgradeReq req = ReliquaryUpgradeReq.parseFrom(payload); + + session.getServer().getInventoryManager().upgradeRelic(session.getPlayer(), req.getTargetReliquaryGuid(), req.getFoodReliquaryGuidListList(), req.getItemParamListList()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java new file mode 100644 index 00000000..19a5cf95 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java @@ -0,0 +1,51 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.GenshinPlayer.SceneLoadState; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketHostPlayerNotify; +import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneInfoNotify; +import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify; +import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; +import emu.grasscutter.server.packet.send.PacketSceneInitFinishRsp; +import emu.grasscutter.server.packet.send.PacketScenePlayerInfoNotify; +import emu.grasscutter.server.packet.send.PacketSceneTeamUpdateNotify; +import emu.grasscutter.server.packet.send.PacketSceneTimeNotify; +import emu.grasscutter.server.packet.send.PacketServerTimeNotify; +import emu.grasscutter.server.packet.send.PacketSyncScenePlayTeamEntityNotify; +import emu.grasscutter.server.packet.send.PacketSyncTeamEntityNotify; +import emu.grasscutter.server.packet.send.PacketWorldDataNotify; +import emu.grasscutter.server.packet.send.PacketWorldPlayerInfoNotify; + +@Opcodes(PacketOpcodes.SceneInitFinishReq) +public class HandlerSceneInitFinishReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Info packets + session.send(new PacketServerTimeNotify()); + session.send(new PacketWorldPlayerInfoNotify(session.getPlayer().getWorld())); + session.send(new PacketWorldDataNotify(session.getPlayer().getWorld())); + session.send(new GenshinPacket(PacketOpcodes.SceneForceUnlockNotify)); + session.send(new PacketHostPlayerNotify(session.getPlayer().getWorld())); + + session.send(new PacketSceneTimeNotify(session.getPlayer())); + session.send(new PacketPlayerGameTimeNotify(session.getPlayer().getWorld(), session.getPlayer())); + session.send(new PacketPlayerEnterSceneInfoNotify(session.getPlayer())); + session.send(new PacketSceneAreaWeatherNotify(session.getPlayer().getWorld(), session.getPlayer())); + session.send(new PacketScenePlayerInfoNotify(session.getPlayer().getWorld())); + session.send(new PacketSceneTeamUpdateNotify(session.getPlayer())); + + session.send(new PacketSyncTeamEntityNotify(session.getPlayer())); + session.send(new PacketSyncScenePlayTeamEntityNotify(session.getPlayer())); + + // Done Packet + session.send(new PacketSceneInitFinishRsp(session.getPlayer())); + + // Set state + session.getPlayer().setSceneLoadState(SceneLoadState.INIT); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneKickPlayerReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneKickPlayerReq.java new file mode 100644 index 00000000..73266448 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneKickPlayerReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneKickPlayerReqOuterClass.SceneKickPlayerReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSceneKickPlayerRsp; + +@Opcodes(PacketOpcodes.SceneKickPlayerReq) +public class HandlerSceneKickPlayerReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SceneKickPlayerReq req = SceneKickPlayerReq.parseFrom(payload); + + if (session.getServer().getMultiplayerManager().kickPlayer(session.getPlayer(), req.getTargetUid())) { + // Success + session.send(new PacketSceneKickPlayerRsp(req.getTargetUid())); + } else { + // Fail + session.send(new PacketSceneKickPlayerRsp()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java new file mode 100644 index 00000000..accbd253 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.SetEntityClientDataNotify) +public class HandlerSetEntityClientDataNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEquipLockStateReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEquipLockStateReq.java new file mode 100644 index 00000000..27bd8b1c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEquipLockStateReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetEquipLockStateReqOuterClass.SetEquipLockStateReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.SetEquipLockStateReq) +public class HandlerSetEquipLockStateReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetEquipLockStateReq req = SetEquipLockStateReq.parseFrom(payload); + + session.getServer().getInventoryManager().lockEquip(session.getPlayer(), req.getTargetEquipGuid(), req.getIsLocked()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetNameCardReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetNameCardReq.java new file mode 100644 index 00000000..158bfc92 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetNameCardReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetNameCardReqOuterClass.SetNameCardReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.SetNameCardReq) +public class HandlerSetNameCardReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetNameCardReq req = SetNameCardReq.parseFrom(payload); + + session.getPlayer().setNameCard(req.getNameCardId()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java new file mode 100644 index 00000000..a45cca5b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -0,0 +1,78 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GenshinData; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerBornDataReqOuterClass.SetPlayerBornDataReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.game.GameSession.SessionState; + +@Opcodes(PacketOpcodes.SetPlayerBornDataReq) +public class HandlerSetPlayerBornDataReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetPlayerBornDataReq req = SetPlayerBornDataReq.parseFrom(payload); + + // Sanity checks + int avatarId = req.getAvatarId(); + int startingSkillDepot = 0; + if (avatarId == GenshinConstants.MAIN_CHARACTER_MALE) { + startingSkillDepot = 504; + } else if (avatarId == GenshinConstants.MAIN_CHARACTER_FEMALE) { + startingSkillDepot = 704; + } else { + return; + } + + String nickname = req.getNickName(); + if (nickname == null) { + nickname = "Traveler"; + } + + // Create character + GenshinPlayer player = new GenshinPlayer(session); + player.setNickname(nickname); + + try { + // Save to db + DatabaseHelper.createPlayer(player, session.getAccount().getPlayerId()); + + // Create avatar + if (player.getAvatars().getAvatarCount() == 0) { + GenshinAvatar mainCharacter = new GenshinAvatar(avatarId); + mainCharacter.setSkillDepot(GenshinData.getAvatarSkillDepotDataMap().get(startingSkillDepot)); + player.addAvatar(mainCharacter); + player.setMainCharacterId(avatarId); + player.setHeadImage(avatarId); + player.getTeamManager().getCurrentSinglePlayerTeamInfo().getAvatars().add(mainCharacter.getAvatarId()); + player.save(); // TODO save player team in different object + } + + // Save account + session.getAccount().setPlayerId(player.getId()); + session.getAccount().save(); + + // Set character + session.setPlayer(player); + + // Login done + session.getPlayer().onLogin(); + session.setState(SessionState.ACTIVE); + + // Born resp packet + session.send(new GenshinPacket(PacketOpcodes.SetPlayerBornDataRsp)); + } catch (Exception e) { + Grasscutter.getLogger().error("Error creating player object: ", e); + session.close(); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerHeadImageReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerHeadImageReq.java new file mode 100644 index 00000000..d478dc28 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerHeadImageReq.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerHeadImageReqOuterClass.SetPlayerHeadImageReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetPlayerHeadImageRsp; + +@Opcodes(PacketOpcodes.SetPlayerHeadImageReq) +public class HandlerSetPlayerHeadImageReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetPlayerHeadImageReq req = SetPlayerHeadImageReq.parseFrom(payload); + + int id = req.getAvatarId(); + + if (session.getPlayer().getAvatars().hasAvatar(id)) { + session.getPlayer().setHeadImage(id); + session.send(new PacketSetPlayerHeadImageRsp(session.getPlayer())); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerNameReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerNameReq.java new file mode 100644 index 00000000..d7babb6c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerNameReq.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerNameReqOuterClass.SetPlayerNameReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetPlayerNameRsp; + +@Opcodes(PacketOpcodes.SetPlayerNameReq) +public class HandlerSetPlayerNameReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + SetPlayerNameReq req = SetPlayerNameReq.parseFrom(payload); + + if (req.getNickName() != null && req.getNickName().length() > 0) { + session.getPlayer().setNickname(req.getNickName()); + session.send(new PacketSetPlayerNameRsp(session.getPlayer())); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerSignatureReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerSignatureReq.java new file mode 100644 index 00000000..a20a78f8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerSignatureReq.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerSignatureReqOuterClass.SetPlayerSignatureReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetPlayerSignatureRsp; + +@Opcodes(PacketOpcodes.SetPlayerSignatureReq) +public class HandlerSetPlayerSignatureReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + SetPlayerSignatureReq req = SetPlayerSignatureReq.parseFrom(payload); + + if (req.getSignature() != null && req.getSignature().length() > 0) { + session.getPlayer().setSignature(req.getSignature()); + session.send(new PacketSetPlayerSignatureRsp(session.getPlayer())); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpAvatarTeamReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpAvatarTeamReq.java new file mode 100644 index 00000000..eed605c6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpAvatarTeamReq.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetUpAvatarTeamReqOuterClass.SetUpAvatarTeamReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.SetUpAvatarTeamReq) +public class HandlerSetUpAvatarTeamReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetUpAvatarTeamReq req = SetUpAvatarTeamReq.parseFrom(payload); + + session.getPlayer().getTeamManager().setupAvatarTeam(req.getTeamId(), req.getAvatarTeamGuidListList()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeoffEquipReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeoffEquipReq.java new file mode 100644 index 00000000..1160cbee --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeoffEquipReq.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TakeoffEquipReqOuterClass.TakeoffEquipReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTakeoffEquipRsp; + +@Opcodes(PacketOpcodes.TakeoffEquipReq) +public class HandlerTakeoffEquipReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TakeoffEquipReq req = TakeoffEquipReq.parseFrom(payload); + + if (session.getPlayer().getInventory().unequipItem(req.getAvatarGuid(), req.getSlot())) { + session.send(new PacketTakeoffEquipRsp(req.getAvatarGuid(), req.getSlot())); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java new file mode 100644 index 00000000..2a9ef200 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTowerAllDataRsp; + +@Opcodes(PacketOpcodes.TowerAllDataReq) +public class HandlerTowerAllDataReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketTowerAllDataRsp()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java new file mode 100644 index 00000000..1f4a9e7f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UnionCmdNotifyOuterClass.UnionCmdNotify; +import emu.grasscutter.net.proto.UnionCmdOuterClass.UnionCmd; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.UnionCmdNotify) +public class HandlerUnionCmdNotify extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + UnionCmdNotify req = UnionCmdNotify.parseFrom(payload); + for (UnionCmd cmd : req.getCmdListList()) { + session.getServer().getPacketHandler().handle(session, cmd.getMessageId(), EMPTY_BYTE_ARRAY, cmd.getBody().toByteArray()); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockAvatarTalentReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockAvatarTalentReq.java new file mode 100644 index 00000000..93cf1cad --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockAvatarTalentReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UnlockAvatarTalentReqOuterClass.UnlockAvatarTalentReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.UnlockAvatarTalentReq) +public class HandlerUnlockAvatarTalentReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + UnlockAvatarTalentReq req = UnlockAvatarTalentReq.parseFrom(payload); + + // Unlock avatar const + session.getServer().getInventoryManager().unlockAvatarConstellation(session.getPlayer(), req.getAvatarGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java new file mode 100644 index 00000000..8a1f4a70 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UseItemReqOuterClass.UseItemReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketUseItemRsp; + +@Opcodes(PacketOpcodes.UseItemReq) +public class HandlerUseItemReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + UseItemReq req = UseItemReq.parseFrom(payload); + + GenshinItem useItem = session.getServer().getInventoryManager().useItem(session.getPlayer(), req.getTargetGuid(), req.getGuid(), req.getCount()); + if (useItem != null) { + session.send(new PacketUseItemRsp(req.getTargetGuid(), useItem)); + } else { + session.send(new PacketUseItemRsp()); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponAwakenReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponAwakenReq.java new file mode 100644 index 00000000..b33510ad --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponAwakenReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WeaponAwakenReqOuterClass.WeaponAwakenReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.WeaponAwakenReq) +public class HandlerWeaponAwakenReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WeaponAwakenReq req = WeaponAwakenReq.parseFrom(payload); + + // Weapon refinement + session.getServer().getInventoryManager().refineWeapon(session.getPlayer(), req.getTargetWeaponGuid(), req.getItemGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponPromoteReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponPromoteReq.java new file mode 100644 index 00000000..0b3c23b4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponPromoteReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WeaponPromoteReqOuterClass.WeaponPromoteReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.WeaponPromoteReq) +public class HandlerWeaponPromoteReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WeaponPromoteReq req = WeaponPromoteReq.parseFrom(payload); + + // Ascend weapon + session.getServer().getInventoryManager().promoteWeapon(session.getPlayer(), req.getTargetWeaponGuid()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponUpgradeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponUpgradeReq.java new file mode 100644 index 00000000..f099c976 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWeaponUpgradeReq.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WeaponUpgradeReqOuterClass.WeaponUpgradeReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.WeaponUpgradeReq) +public class HandlerWeaponUpgradeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WeaponUpgradeReq req = WeaponUpgradeReq.parseFrom(payload); + + // Level up weapon + session.getServer().getInventoryManager().upgradeWeapon( + session.getPlayer(), + req.getTargetWeaponGuid(), + req.getFoodWeaponGuidListList(), + req.getItemParamListList() + ); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWearEquipReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWearEquipReq.java new file mode 100644 index 00000000..3907aee8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWearEquipReq.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WearEquipReqOuterClass.WearEquipReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketWearEquipRsp; + +@Opcodes(PacketOpcodes.WearEquipReq) +public class HandlerWearEquipReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WearEquipReq req = WearEquipReq.parseFrom(payload); + + if (session.getPlayer().getInventory().equipItem(req.getAvatarGuid(), req.getEquipGuid())) { + session.send(new PacketWearEquipRsp(req.getAvatarGuid(), req.getEquipGuid())); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java new file mode 100644 index 00000000..cbe95a32 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.WorldPlayerReviveReq) +public class HandlerWorldPlayerReviveReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.getPlayer().getTeamManager().respawnTeam(); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/Packet.java b/src/main/java/emu/grasscutter/server/packet/send/Packet.java new file mode 100644 index 00000000..70306892 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/Packet.java @@ -0,0 +1,12 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; + +public class Packet extends GenshinPacket { + + public Packet() { + super(PacketOpcodes.NONE); + + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java new file mode 100644 index 00000000..a6e1fb45 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilityChangeNotifyOuterClass.AbilityChangeNotify; + +public class PacketAbilityChangeNotify extends GenshinPacket { + + public PacketAbilityChangeNotify(EntityAvatar entity) { + super(PacketOpcodes.AbilityChangeNotify); + + AbilityChangeNotify proto = AbilityChangeNotify.newBuilder() + .setEntityId(entity.getId()) + .setAbilityControlBlock(entity.getAbilityControlBlock()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityInvocationsNotify.java new file mode 100644 index 00000000..5b1b19a4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityInvocationsNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilityInvocationsNotifyOuterClass.AbilityInvocationsNotify; +import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; + +public class PacketAbilityInvocationsNotify extends GenshinPacket { + + public PacketAbilityInvocationsNotify(AbilityInvokeEntry entry) { + super(PacketOpcodes.AbilityInvocationsNotify, true); + + AbilityInvocationsNotify proto = AbilityInvocationsNotify.newBuilder() + .addInvokes(entry) + .build(); + + this.setData(proto); + } + + public PacketAbilityInvocationsNotify(List entries) { + super(PacketOpcodes.AbilityInvocationsNotify, true); + + AbilityInvocationsNotify proto = AbilityInvocationsNotify.newBuilder() + .addAllInvokes(entries) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendNotify.java new file mode 100644 index 00000000..a0f2e428 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AskAddFriendNotifyOuterClass.AskAddFriendNotify; + +public class PacketAskAddFriendNotify extends GenshinPacket { + + public PacketAskAddFriendNotify(Friendship friendship) { + super(PacketOpcodes.AskAddFriendNotify); + + AskAddFriendNotify proto = AskAddFriendNotify.newBuilder() + .setTargetUid(friendship.getFriendId()) + .setTargetFriendBrief(friendship.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendRsp.java new file mode 100644 index 00000000..df46ac29 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAskAddFriendRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AskAddFriendRspOuterClass.AskAddFriendRsp; + +public class PacketAskAddFriendRsp extends GenshinPacket { + + public PacketAskAddFriendRsp(int targetUid) { + super(PacketOpcodes.AskAddFriendRsp); + + AskAddFriendRsp proto = AskAddFriendRsp.newBuilder() + .setTargetUid(targetUid) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarAddNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarAddNotify.java new file mode 100644 index 00000000..620165d5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarAddNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarAddNotifyOuterClass.AvatarAddNotify; + +public class PacketAvatarAddNotify extends GenshinPacket { + + public PacketAvatarAddNotify(GenshinAvatar avatar, boolean addedToTeam) { + super(PacketOpcodes.AvatarAddNotify); + + AvatarAddNotify proto = AvatarAddNotify.newBuilder() + .setAvatar(avatar.toProto()) + .setIsInTeam(addedToTeam) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeNotify.java new file mode 100644 index 00000000..0319fc4c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarChangeCostumeNotifyOuterClass.AvatarChangeCostumeNotify; + +public class PacketAvatarChangeCostumeNotify extends GenshinPacket { + + public PacketAvatarChangeCostumeNotify(EntityAvatar entity) { + super(PacketOpcodes.AvatarChangeCostumeNotify); + + AvatarChangeCostumeNotify proto = AvatarChangeCostumeNotify.newBuilder() + .setEntity(entity.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeRsp.java new file mode 100644 index 00000000..66c1d505 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarChangeCostumeRsp.java @@ -0,0 +1,29 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarChangeCostumeRspOuterClass.AvatarChangeCostumeRsp; + +public class PacketAvatarChangeCostumeRsp extends GenshinPacket { + + public PacketAvatarChangeCostumeRsp(long avatarGuid, int costumeId) { + super(PacketOpcodes.AvatarChangeCostumeRsp); + + AvatarChangeCostumeRsp proto = AvatarChangeCostumeRsp.newBuilder() + .setAvatarGuid(avatarGuid) + .setCostumeId(costumeId) + .build(); + + this.setData(proto); + } + + public PacketAvatarChangeCostumeRsp() { + super(PacketOpcodes.AvatarChangeCostumeRsp); + + AvatarChangeCostumeRsp proto = AvatarChangeCostumeRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDataNotify.java new file mode 100644 index 00000000..a05a2dab --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDataNotify.java @@ -0,0 +1,44 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Map.Entry; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.TeamInfo; +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarDataNotifyOuterClass.AvatarDataNotify; +import emu.grasscutter.net.proto.AvatarTeamOuterClass.AvatarTeam; + +public class PacketAvatarDataNotify extends GenshinPacket { + + public PacketAvatarDataNotify(GenshinPlayer player) { + super(PacketOpcodes.AvatarDataNotify, 2); + + AvatarDataNotify.Builder proto = AvatarDataNotify.newBuilder() + .setCurAvatarTeamId(player.getTeamManager().getCurrentTeamId()) + .setChooseAvatarGuid(player.getTeamManager().getCurrentCharacterGuid()) + .addAllOwnedFlycloakList(player.getFlyCloakList()) + .addAllOwnedCostumeList(player.getCostumeList()); + + for (GenshinAvatar avatar : player.getAvatars()) { + proto.addAvatarList(avatar.toProto()); + } + + for (Entry entry : player.getTeamManager().getTeams().entrySet()) { + TeamInfo teamInfo = entry.getValue(); + AvatarTeam.Builder avatarTeam = AvatarTeam.newBuilder() + .setTeamName(teamInfo.getName()); + + for (int i = 0; i < teamInfo.getAvatars().size(); i++) { + GenshinAvatar avatar = player.getAvatars().getAvatarById(teamInfo.getAvatars().get(i)); + avatarTeam.addAvatarGuidList(avatar.getGuid()); + } + + proto.putAvatarTeamMap(entry.getKey(), avatarTeam.build()); + } + + this.setData(proto.build()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDieAnimationEndRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDieAnimationEndRsp.java new file mode 100644 index 00000000..3fa913a0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarDieAnimationEndRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarDieAnimationEndRspOuterClass.AvatarDieAnimationEndRsp; + +public class PacketAvatarDieAnimationEndRsp extends GenshinPacket { + + public PacketAvatarDieAnimationEndRsp(long dieGuid, int skillId) { + super(PacketOpcodes.AvatarDieAnimationEndRsp); + + AvatarDieAnimationEndRsp proto = AvatarDieAnimationEndRsp.newBuilder() + .setDieGuid(dieGuid) + .setSkillId(skillId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarEquipChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarEquipChangeNotify.java new file mode 100644 index 00000000..000c72d3 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarEquipChangeNotify.java @@ -0,0 +1,39 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.inventory.EquipType; +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarEquipChangeNotifyOuterClass.AvatarEquipChangeNotify; + +public class PacketAvatarEquipChangeNotify extends GenshinPacket { + + public PacketAvatarEquipChangeNotify(GenshinAvatar avatar, GenshinItem item) { + super(PacketOpcodes.AvatarEquipChangeNotify); + + AvatarEquipChangeNotify.Builder proto = AvatarEquipChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setEquipType(item.getEquipSlot()) + .setItemId(item.getItemId()) + .setEquipGuid(item.getGuid()); + + if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { + proto.setWeapon(item.createSceneWeaponInfo()); + } else { + proto.setReliquary(item.createSceneReliquaryInfo()); + } + + this.setData(proto); + } + + public PacketAvatarEquipChangeNotify(GenshinAvatar avatar, EquipType slot) { + super(PacketOpcodes.AvatarEquipChangeNotify); + + AvatarEquipChangeNotify.Builder proto = AvatarEquipChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setEquipType(slot.getValue()); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropNotify.java new file mode 100644 index 00000000..bb68c5d1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarFightPropNotifyOuterClass.AvatarFightPropNotify; + +public class PacketAvatarFightPropNotify extends GenshinPacket { + + public PacketAvatarFightPropNotify(GenshinAvatar avatar) { + super(PacketOpcodes.AvatarFightPropNotify); + + AvatarFightPropNotify proto = AvatarFightPropNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .putAllFightPropMap(avatar.getFightProperties()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropUpdateNotify.java new file mode 100644 index 00000000..a939ba05 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFightPropUpdateNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarFightPropUpdateNotifyOuterClass.AvatarFightPropUpdateNotify; + +public class PacketAvatarFightPropUpdateNotify extends GenshinPacket { + + public PacketAvatarFightPropUpdateNotify(GenshinAvatar avatar, FightProperty prop) { + super(PacketOpcodes.AvatarFightPropUpdateNotify); + + AvatarFightPropUpdateNotify proto = AvatarFightPropUpdateNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .putFightPropMap(prop.getId(), avatar.getFightProperty(prop)) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFlycloakChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFlycloakChangeNotify.java new file mode 100644 index 00000000..88efecfe --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarFlycloakChangeNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarFlycloakChangeNotifyOuterClass.AvatarFlycloakChangeNotify; + +public class PacketAvatarFlycloakChangeNotify extends GenshinPacket { + + public PacketAvatarFlycloakChangeNotify(GenshinAvatar avatar) { + super(PacketOpcodes.AvatarFlycloakChangeNotify); + + AvatarFlycloakChangeNotify proto = AvatarFlycloakChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setFlycloakId(avatar.getFlyCloak()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainCostumeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainCostumeNotify.java new file mode 100644 index 00000000..74946d95 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainCostumeNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarGainCostumeNotifyOuterClass.AvatarGainCostumeNotify; + +public class PacketAvatarGainCostumeNotify extends GenshinPacket { + + public PacketAvatarGainCostumeNotify(int costumeId) { + super(PacketOpcodes.AvatarGainCostumeNotify); + + AvatarGainCostumeNotify proto = AvatarGainCostumeNotify.newBuilder() + .setCostumeId(costumeId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainFlycloakNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainFlycloakNotify.java new file mode 100644 index 00000000..a3dfacc1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarGainFlycloakNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarGainFlycloakNotifyOuterClass.AvatarGainFlycloakNotify; + +public class PacketAvatarGainFlycloakNotify extends GenshinPacket { + + public PacketAvatarGainFlycloakNotify(int flycloak) { + super(PacketOpcodes.AvatarGainFlycloakNotify); + + AvatarGainFlycloakNotify proto = AvatarGainFlycloakNotify.newBuilder() + .setFlycloakId(flycloak) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java new file mode 100644 index 00000000..d9acb5d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarLifeStateChangeNotifyOuterClass.AvatarLifeStateChangeNotify; + +public class PacketAvatarLifeStateChangeNotify extends GenshinPacket { + + public PacketAvatarLifeStateChangeNotify(GenshinAvatar avatar) { + super(PacketOpcodes.AvatarLifeStateChangeNotify); + + AvatarLifeStateChangeNotify proto = AvatarLifeStateChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setLifeState(avatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0 ? LifeState.LIFE_ALIVE.getValue() : LifeState.LIFE_DEAD.getValue()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPromoteRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPromoteRsp.java new file mode 100644 index 00000000..feef149e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPromoteRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarPromoteRspOuterClass.AvatarPromoteRsp; + +public class PacketAvatarPromoteRsp extends GenshinPacket { + + public PacketAvatarPromoteRsp(GenshinAvatar avatar) { + super(PacketOpcodes.AvatarPromoteRsp); + + AvatarPromoteRsp proto = AvatarPromoteRsp.newBuilder() + .setGuid(avatar.getGuid()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPropNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPropNotify.java new file mode 100644 index 00000000..d1c04d9d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarPropNotify.java @@ -0,0 +1,35 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarPropNotifyOuterClass.AvatarPropNotify; + +public class PacketAvatarPropNotify extends GenshinPacket { + public PacketAvatarPropNotify(GenshinAvatar avatar) { + super(PacketOpcodes.AvatarPropNotify); + + AvatarPropNotify proto = AvatarPropNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .putPropMap(PlayerProperty.PROP_LEVEL.getId(), avatar.getLevel()) + .putPropMap(PlayerProperty.PROP_EXP.getId(), avatar.getExp()) + .putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), avatar.getPromoteLevel()) + .putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), 0) + .putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), 0) + .build(); + + this.setData(proto); + } + + public PacketAvatarPropNotify(GenshinAvatar avatar, PlayerProperty prop, int value) { + super(PacketOpcodes.AvatarPropNotify); + + AvatarPropNotify proto = AvatarPropNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .putPropMap(prop.getId(), value) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillChangeNotify.java new file mode 100644 index 00000000..ba787ee7 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillChangeNotify.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarSkillChangeNotifyOuterClass.AvatarSkillChangeNotify; + +public class PacketAvatarSkillChangeNotify extends GenshinPacket { + + public PacketAvatarSkillChangeNotify(GenshinAvatar avatar, int skillId, int oldLevel, int curLevel) { + super(PacketOpcodes.AvatarSkillChangeNotify); + + AvatarSkillChangeNotify proto = AvatarSkillChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setEntityId(avatar.getEntityId()) + .setSkillDepotId(avatar.getSkillDepotId()) + .setAvatarSkillId(skillId) + .setOldLevel(oldLevel) + .setCurLevel(curLevel) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillUpgradeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillUpgradeRsp.java new file mode 100644 index 00000000..21163e90 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarSkillUpgradeRsp.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarSkillUpgradeRspOuterClass.AvatarSkillUpgradeRsp; + +public class PacketAvatarSkillUpgradeRsp extends GenshinPacket { + + public PacketAvatarSkillUpgradeRsp(GenshinAvatar avatar, int skillId, int oldLevel, int newLevel) { + super(PacketOpcodes.AvatarSkillUpgradeRsp); + + AvatarSkillUpgradeRsp proto = AvatarSkillUpgradeRsp.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setAvatarSkillId(skillId) + .setOldLevel(oldLevel) + .setCurLevel(newLevel) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarTeamUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarTeamUpdateNotify.java new file mode 100644 index 00000000..519584cc --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarTeamUpdateNotify.java @@ -0,0 +1,35 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Map.Entry; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.TeamInfo; +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarTeamOuterClass.AvatarTeam; +import emu.grasscutter.net.proto.AvatarTeamUpdateNotifyOuterClass.AvatarTeamUpdateNotify; + +public class PacketAvatarTeamUpdateNotify extends GenshinPacket { + + public PacketAvatarTeamUpdateNotify(GenshinPlayer player) { + super(PacketOpcodes.AvatarTeamUpdateNotify); + + AvatarTeamUpdateNotify.Builder proto = AvatarTeamUpdateNotify.newBuilder(); + + for (Entry entry : player.getTeamManager().getTeams().entrySet()) { + TeamInfo teamInfo = entry.getValue(); + AvatarTeam.Builder avatarTeam = AvatarTeam.newBuilder() + .setTeamName(teamInfo.getName()); + + for (int i = 0; i < teamInfo.getAvatars().size(); i++) { + GenshinAvatar avatar = player.getAvatars().getAvatarById(teamInfo.getAvatars().get(i)); + avatarTeam.addAvatarGuidList(avatar.getGuid()); + } + + proto.putAvatarTeamMap(entry.getKey(), avatarTeam.build()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUnlockTalentNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUnlockTalentNotify.java new file mode 100644 index 00000000..4c0b11ae --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUnlockTalentNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarUnlockTalentNotifyOuterClass.AvatarUnlockTalentNotify; + +public class PacketAvatarUnlockTalentNotify extends GenshinPacket { + + public PacketAvatarUnlockTalentNotify(GenshinAvatar avatar, int talentId) { + super(PacketOpcodes.AvatarUnlockTalentNotify); + + AvatarUnlockTalentNotify proto = AvatarUnlockTalentNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setEntityId(avatar.getEntityId()) + .setTalentId(talentId) + .setSkillDepotId(avatar.getSkillDepotId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUpgradeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUpgradeRsp.java new file mode 100644 index 00000000..031313ea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarUpgradeRsp.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Map; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarUpgradeRspOuterClass.AvatarUpgradeRsp; + +public class PacketAvatarUpgradeRsp extends GenshinPacket { + + public PacketAvatarUpgradeRsp(GenshinAvatar avatar, int oldLevel, Map oldFightPropMap) { + super(PacketOpcodes.AvatarUpgradeRsp); + + this.buildHeader(0); + + AvatarUpgradeRsp proto = AvatarUpgradeRsp.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setOldLevel(oldLevel) + .setCurLevel(avatar.getLevel()) + .putAllOldFightPropMap(oldFightPropMap) + .putAllCurFightPropMap(avatar.getFightProperties()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarWearFlycloakRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarWearFlycloakRsp.java new file mode 100644 index 00000000..b95cfafc --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarWearFlycloakRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarWearFlycloakRspOuterClass.AvatarWearFlycloakRsp; + +public class PacketAvatarWearFlycloakRsp extends GenshinPacket { + public PacketAvatarWearFlycloakRsp(long avatarGuid, int costumeId) { + super(PacketOpcodes.AvatarWearFlycloakRsp); + + AvatarWearFlycloakRsp proto = AvatarWearFlycloakRsp.newBuilder() + .setAvatarGuid(avatarGuid) + .setFlycloakId(costumeId) + .build(); + + this.setData(proto); + } + + public PacketAvatarWearFlycloakRsp() { + super(PacketOpcodes.AvatarWearFlycloakRsp); + + AvatarWearFlycloakRsp proto = AvatarWearFlycloakRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCalcWeaponUpgradeReturnItemsRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCalcWeaponUpgradeReturnItemsRsp.java new file mode 100644 index 00000000..d3a30a70 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCalcWeaponUpgradeReturnItemsRsp.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.Retcode; +import emu.grasscutter.net.proto.CalcWeaponUpgradeReturnItemsRspOuterClass.CalcWeaponUpgradeReturnItemsRsp; +import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; + +public class PacketCalcWeaponUpgradeReturnItemsRsp extends GenshinPacket { + + public PacketCalcWeaponUpgradeReturnItemsRsp(long itemGuid, List returnItems) { + super(PacketOpcodes.CalcWeaponUpgradeReturnItemsRsp); + + CalcWeaponUpgradeReturnItemsRsp proto = CalcWeaponUpgradeReturnItemsRsp.newBuilder() + .setTargetWeaponGuid(itemGuid) + .addAllItemParamList(returnItems) + .build(); + + this.setData(proto); + } + + public PacketCalcWeaponUpgradeReturnItemsRsp() { + super(PacketOpcodes.CalcWeaponUpgradeReturnItemsRsp); + + CalcWeaponUpgradeReturnItemsRsp proto = CalcWeaponUpgradeReturnItemsRsp.newBuilder() + .setRetcode(Retcode.FAIL) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeAvatarRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeAvatarRsp.java new file mode 100644 index 00000000..ac222a43 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeAvatarRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeAvatarRspOuterClass.ChangeAvatarRsp; + +public class PacketChangeAvatarRsp extends GenshinPacket { + + public PacketChangeAvatarRsp(long guid) { + super(PacketOpcodes.ChangeAvatarRsp); + + ChangeAvatarRsp p = ChangeAvatarRsp.newBuilder() + .setRetcode(0) + .setCurrGuid(guid) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java new file mode 100644 index 00000000..5a97331f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeGameTimeRspOuterClass.ChangeGameTimeRsp; + +public class PacketChangeGameTimeRsp extends GenshinPacket { + + public PacketChangeGameTimeRsp(World world) { + super(PacketOpcodes.ChangeGameTimeRsp); + + ChangeGameTimeRsp proto = ChangeGameTimeRsp.newBuilder() + .setCurGameTime(world.getTime()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeMpTeamAvatarRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeMpTeamAvatarRsp.java new file mode 100644 index 00000000..36d2ed24 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeMpTeamAvatarRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.TeamInfo; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeMpTeamAvatarRspOuterClass.ChangeMpTeamAvatarRsp; + +public class PacketChangeMpTeamAvatarRsp extends GenshinPacket { + + public PacketChangeMpTeamAvatarRsp(GenshinPlayer player, TeamInfo teamInfo) { + super(PacketOpcodes.ChangeMpTeamAvatarRsp); + + ChangeMpTeamAvatarRsp.Builder proto = ChangeMpTeamAvatarRsp.newBuilder() + .setCurAvatarGuid(player.getTeamManager().getCurrentCharacterGuid()); + + for (int avatarId : teamInfo.getAvatars()) { + proto.addAvatarGuidList(player.getAvatars().getAvatarById(avatarId).getGuid()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeTeamNameRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeTeamNameRsp.java new file mode 100644 index 00000000..52c6bccb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeTeamNameRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeTeamNameRspOuterClass.ChangeTeamNameRsp; + +public class PacketChangeTeamNameRsp extends GenshinPacket { + + public PacketChangeTeamNameRsp(int teamId, String teamName) { + super(PacketOpcodes.ChangeTeamNameRsp); + + ChangeTeamNameRsp proto = ChangeTeamNameRsp.newBuilder() + .setTeamId(teamId) + .setTeamName(teamName) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChooseCurAvatarTeamRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChooseCurAvatarTeamRsp.java new file mode 100644 index 00000000..2af34109 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChooseCurAvatarTeamRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChooseCurAvatarTeamRspOuterClass.ChooseCurAvatarTeamRsp; + +public class PacketChooseCurAvatarTeamRsp extends GenshinPacket { + + public PacketChooseCurAvatarTeamRsp(int teamId) { + super(PacketOpcodes.ChooseCurAvatarTeamRsp); + + ChooseCurAvatarTeamRsp proto = ChooseCurAvatarTeamRsp.newBuilder() + .setCurTeamId(teamId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCombatInvocationsNotify.java new file mode 100644 index 00000000..c2031065 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCombatInvocationsNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; +import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; + +public class PacketCombatInvocationsNotify extends GenshinPacket { + + public PacketCombatInvocationsNotify(CombatInvokeEntry entry) { + super(PacketOpcodes.CombatInvocationsNotify, true); + + CombatInvocationsNotify proto = CombatInvocationsNotify.newBuilder() + .addInvokeList(entry) + .build(); + + this.setData(proto); + } + + public PacketCombatInvocationsNotify(List entries) { + super(PacketOpcodes.CombatInvocationsNotify, true); + + CombatInvocationsNotify proto = CombatInvocationsNotify.newBuilder() + .addAllInvokeList(entries) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDealAddFriendRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDealAddFriendRsp.java new file mode 100644 index 00000000..590506c1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDealAddFriendRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType; +import emu.grasscutter.net.proto.DealAddFriendRspOuterClass.DealAddFriendRsp; + +public class PacketDealAddFriendRsp extends GenshinPacket { + + public PacketDealAddFriendRsp(int targetUid, DealAddFriendResultType result) { + super(PacketOpcodes.DealAddFriendRsp); + + DealAddFriendRsp proto = DealAddFriendRsp.newBuilder() + .setTargetUid(targetUid) + .setDealAddFriendResult(result) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDelTeamEntityNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDelTeamEntityNotify.java new file mode 100644 index 00000000..8e6bd10f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDelTeamEntityNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DelTeamEntityNotifyOuterClass.DelTeamEntityNotify; + +public class PacketDelTeamEntityNotify extends GenshinPacket { + + public PacketDelTeamEntityNotify(int sceneId, int teamEntityId) { + super(PacketOpcodes.DelTeamEntityNotify); + + DelTeamEntityNotify proto = DelTeamEntityNotify.newBuilder() + .setSceneId(sceneId) + .addDelEntityIdList(teamEntityId) + .build(); + + this.setData(proto); + } + + public PacketDelTeamEntityNotify(int sceneId, List list) { + super(PacketOpcodes.DelTeamEntityNotify); + + DelTeamEntityNotify proto = DelTeamEntityNotify.newBuilder() + .setSceneId(sceneId) + .addAllDelEntityIdList(list) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendNotify.java new file mode 100644 index 00000000..7b41b14a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DeleteFriendNotifyOuterClass.DeleteFriendNotify; + +public class PacketDeleteFriendNotify extends GenshinPacket { + + public PacketDeleteFriendNotify(int targetUid) { + super(PacketOpcodes.DeleteFriendNotify); + + DeleteFriendNotify proto = DeleteFriendNotify.newBuilder() + .setTargetUid(targetUid) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendRsp.java new file mode 100644 index 00000000..bfe7c2ce --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDeleteFriendRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DeleteFriendRspOuterClass.DeleteFriendRsp; + +public class PacketDeleteFriendRsp extends GenshinPacket { + + public PacketDeleteFriendRsp(int targetUid) { + super(PacketOpcodes.DeleteFriendRsp); + + DeleteFriendRsp proto = DeleteFriendRsp.newBuilder() + .setTargetUid(targetUid) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDestroyMaterialRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDestroyMaterialRsp.java new file mode 100644 index 00000000..76bc1ebb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDestroyMaterialRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DestroyMaterialRspOuterClass.DestroyMaterialRsp; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + +public class PacketDestroyMaterialRsp extends GenshinPacket { + + public PacketDestroyMaterialRsp(Int2IntOpenHashMap returnMaterialMap) { + super(PacketOpcodes.DestroyMaterialRsp); + + DestroyMaterialRsp.Builder proto = DestroyMaterialRsp.newBuilder(); + + for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) { + proto.addItemIdList(e.getIntKey()); + proto.addItemCountList(e.getIntValue()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java new file mode 100644 index 00000000..090fcfc2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java @@ -0,0 +1,41 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.game.gacha.GachaBanner; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DoGachaRspOuterClass.DoGachaRsp; +import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem; + +public class PacketDoGachaRsp extends GenshinPacket { + + public PacketDoGachaRsp(GachaBanner banner, List list) { + super(PacketOpcodes.DoGachaRsp); + + DoGachaRsp p = DoGachaRsp.newBuilder() + .setGachaType(banner.getGachaType()) + .setGachaScheduleId(banner.getScheduleId()) + .setGachaTimes(list.size()) + .setNewGachaRandom(12345) + .setLeftGachaTimes(Integer.MAX_VALUE) + .setCostItemId(banner.getCostItem()) + .setCostItemNum(1) + .setTenCostItemId(banner.getCostItem()) + .setTenCostItemNum(10) + .addAllGachaItemList(list) + .build(); + + this.setData(p); + } + + public PacketDoGachaRsp() { + super(PacketOpcodes.DoGachaRsp); + + DoGachaRsp p = DoGachaRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneDoneRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneDoneRsp.java new file mode 100644 index 00000000..6f1314a2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneDoneRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterSceneDoneRspOuterClass.EnterSceneDoneRsp; + +public class PacketEnterSceneDoneRsp extends GenshinPacket { + + public PacketEnterSceneDoneRsp(GenshinPlayer player) { + super(PacketOpcodes.EnterSceneDoneRsp); + + EnterSceneDoneRsp p = EnterSceneDoneRsp.newBuilder() + .setEnterSceneToken(player.getEnterSceneToken()) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEnterScenePeerNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterScenePeerNotify.java new file mode 100644 index 00000000..07c2bbf2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterScenePeerNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterScenePeerNotifyOuterClass.EnterScenePeerNotify; + +public class PacketEnterScenePeerNotify extends GenshinPacket { + + public PacketEnterScenePeerNotify(GenshinPlayer player) { + super(PacketOpcodes.EnterScenePeerNotify); + + EnterScenePeerNotify proto = EnterScenePeerNotify.newBuilder() + .setDestSceneId(player.getSceneId()) + .setPeerId(player.getPeerId()) + .setHostPeerId(player.getWorld().getHost().getPeerId()) + .setEnterSceneToken(player.getEnterSceneToken()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneReadyRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneReadyRsp.java new file mode 100644 index 00000000..c426bc72 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterSceneReadyRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterSceneReadyRspOuterClass.EnterSceneReadyRsp; + +public class PacketEnterSceneReadyRsp extends GenshinPacket { + + public PacketEnterSceneReadyRsp(GenshinPlayer player) { + super(PacketOpcodes.EnterSceneReadyRsp, 11); + + EnterSceneReadyRsp p = EnterSceneReadyRsp.newBuilder() + .setEnterSceneToken(player.getEnterSceneToken()) + .build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEnterWorldAreaRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterWorldAreaRsp.java new file mode 100644 index 00000000..0d907f31 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEnterWorldAreaRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterWorldAreaReqOuterClass.EnterWorldAreaReq; +import emu.grasscutter.net.proto.EnterWorldAreaRspOuterClass.EnterWorldAreaRsp; + +public class PacketEnterWorldAreaRsp extends GenshinPacket { + + public PacketEnterWorldAreaRsp(int clientSequence, EnterWorldAreaReq enterWorld) { + super(PacketOpcodes.EnterWorldAreaRsp, clientSequence); + + EnterWorldAreaRsp p = EnterWorldAreaRsp.newBuilder() + .setAreaType(enterWorld.getAreaType()) + .setAreaId(enterWorld.getAreaId()) + .build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityAiSyncNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityAiSyncNotify.java new file mode 100644 index 00000000..36e4d813 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityAiSyncNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AiSyncInfoOuterClass.AiSyncInfo; +import emu.grasscutter.net.proto.EntityAiSyncNotifyOuterClass.EntityAiSyncNotify; + +public class PacketEntityAiSyncNotify extends GenshinPacket { + + public PacketEntityAiSyncNotify(EntityAiSyncNotify notify) { + super(PacketOpcodes.EntityAiSyncNotify, true); + + EntityAiSyncNotify.Builder proto = EntityAiSyncNotify.newBuilder(); + + for (int monsterId : notify.getLocalAvatarAlertedMonsterListList()) { + proto.addInfoList(AiSyncInfo.newBuilder().setEntityId(monsterId)); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java new file mode 100644 index 00000000..8d76891e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EntityFightPropUpdateNotifyOuterClass.EntityFightPropUpdateNotify; + +public class PacketEntityFightPropUpdateNotify extends GenshinPacket { + + public PacketEntityFightPropUpdateNotify(GenshinEntity entity, FightProperty prop) { + super(PacketOpcodes.EntityFightPropUpdateNotify); + + EntityFightPropUpdateNotify proto = EntityFightPropUpdateNotify.newBuilder() + .setEntityId(entity.getId()) + .putFightPropMap(prop.getId(), entity.getFightProperty(prop)) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java new file mode 100644 index 00000000..3cb2661d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java @@ -0,0 +1,31 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp; +import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; + +public class PacketGadgetInteractRsp extends GenshinPacket { + public PacketGadgetInteractRsp(EntityGadget gadget, InteractType interact) { + super(PacketOpcodes.GadgetInteractRsp); + + GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() + .setGadgetEntityId(gadget.getId()) + .setInteractType(interact) + .setGadgetId(gadget.getGadgetId()) + .build(); + + this.setData(proto); + } + + public PacketGadgetInteractRsp() { + super(PacketOpcodes.GadgetInteractRsp); + + GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java new file mode 100644 index 00000000..8b8a5cff --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetActivityInfoRspOuterClass.GetActivityInfoRsp; + +public class PacketGetActivityInfoRsp extends GenshinPacket { + public PacketGetActivityInfoRsp() { + super(PacketOpcodes.GetActivityInfoRsp); + + GetActivityInfoRsp proto = GetActivityInfoRsp.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllUnlockNameCardRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllUnlockNameCardRsp.java new file mode 100644 index 00000000..0ac6516d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllUnlockNameCardRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetAllUnlockNameCardRspOuterClass.GetAllUnlockNameCardRsp; + +public class PacketGetAllUnlockNameCardRsp extends GenshinPacket { + + public PacketGetAllUnlockNameCardRsp(GenshinPlayer player) { + super(PacketOpcodes.GetAllUnlockNameCardRsp); + + GetAllUnlockNameCardRsp proto = GetAllUnlockNameCardRsp.newBuilder() + .addAllNameCardList(player.getNameCardList()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAuthkeyRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAuthkeyRsp.java new file mode 100644 index 00000000..da250fec --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAuthkeyRsp.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetAuthkeyRspOuterClass.GetAuthkeyRsp; + +public class PacketGetAuthkeyRsp extends GenshinPacket { + + public PacketGetAuthkeyRsp() { + super(PacketOpcodes.GetAuthkeyRsp); + + GetAuthkeyRsp proto = GetAuthkeyRsp.newBuilder().setRetcode(1).build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java new file mode 100644 index 00000000..6772516a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java @@ -0,0 +1,14 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.gacha.GachaManager; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; + +public class PacketGetGachaInfoRsp extends GenshinPacket { + + public PacketGetGachaInfoRsp(GachaManager manager) { + super(PacketOpcodes.GetGachaInfoRsp); + + this.setData(manager.toProto()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerAskFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerAskFriendListRsp.java new file mode 100644 index 00000000..14bd3432 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerAskFriendListRsp.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetPlayerAskFriendListRspOuterClass.GetPlayerAskFriendListRsp; + +public class PacketGetPlayerAskFriendListRsp extends GenshinPacket { + + public PacketGetPlayerAskFriendListRsp(GenshinPlayer player) { + super(PacketOpcodes.GetPlayerAskFriendListRsp); + + GetPlayerAskFriendListRsp.Builder proto = GetPlayerAskFriendListRsp.newBuilder(); + + for (Friendship friendship : player.getFriendsList().getPendingFriends().values()) { + if (friendship.getAskerId() == player.getId()) { + continue; + } + proto.addAskFriendList(friendship.toProto()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java new file mode 100644 index 00000000..bf0630ee --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java @@ -0,0 +1,48 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief; +import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState; +import emu.grasscutter.net.proto.GetPlayerFriendListRspOuterClass.GetPlayerFriendListRsp; +import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage; + +public class PacketGetPlayerFriendListRsp extends GenshinPacket { + + public PacketGetPlayerFriendListRsp(GenshinPlayer player) { + super(PacketOpcodes.GetPlayerFriendListRsp); + + FriendBrief serverFriend = FriendBrief.newBuilder() + .setUid(GenshinConstants.SERVER_CONSOLE_UID) + .setNickname("Server") + .setLevel(1) + .setAvatar(HeadImage.newBuilder().setAvatarId(GenshinConstants.MAIN_CHARACTER_FEMALE)) + .setWorldLevel(0) + .setSignature("") + .setLastActiveTime((int) (System.currentTimeMillis() / 1000f)) + .setIsMpModeAvailable(true) + .setNameCardId(210001) + .setOnlineState(FriendOnlineState.FRIEND_ONLINE) + .setParam(1) + .setUnk1(1) + .setUnk2(3) + .build(); + + GetPlayerFriendListRsp.Builder proto = GetPlayerFriendListRsp.newBuilder().addFriendList(serverFriend); + + for (Friendship friendship : player.getFriendsList().getFriends().values()) { + proto.addFriendList(friendship.toProto()); + } + for (Friendship friendship : player.getFriendsList().getPendingFriends().values()) { + if (friendship.getAskerId() == player.getId()) { + continue; + } + proto.addAskFriendList(friendship.toProto()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerSocialDetailRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerSocialDetailRsp.java new file mode 100644 index 00000000..5529d722 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerSocialDetailRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetPlayerSocialDetailRspOuterClass.GetPlayerSocialDetailRsp; +import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; + +public class PacketGetPlayerSocialDetailRsp extends GenshinPacket { + + public PacketGetPlayerSocialDetailRsp(SocialDetail.Builder detail) { + super(PacketOpcodes.GetPlayerSocialDetailRsp); + + GetPlayerSocialDetailRsp.Builder proto = GetPlayerSocialDetailRsp.newBuilder(); + + if (detail != null) { + proto.setDetailData(detail); + } else { + proto.setRetcode(1); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java new file mode 100644 index 00000000..2306aa0a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java @@ -0,0 +1,35 @@ +package emu.grasscutter.server.packet.send; + +import com.google.protobuf.ByteString; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Crypto; + +public class PacketGetPlayerTokenRsp extends GenshinPacket { + + public PacketGetPlayerTokenRsp(GameSession session, boolean doesPlayerExist) { + super(PacketOpcodes.GetPlayerTokenRsp, true); + + this.setUseDispatchKey(true); + + GetPlayerTokenRsp p = GetPlayerTokenRsp.newBuilder() + .setPlayerUid(session.getAccount().getPlayerId()) + .setAccountToken(session.getAccount().getToken()) + .setAccountType(1) + .setIsProficientPlayer(doesPlayerExist) // Not sure where this goes + .setSecretKey(Crypto.ENCRYPT_SEED) + .setSecretKeyBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER)) + .setPlatformType(3) + .setChannelId(1) + .setCountryCode("US") + .setUnk1("c25-314dd05b0b5f") + .setUnk3(3) + .setClientIp(session.getAddress().getAddress().getHostAddress()) + .build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetSceneAreaRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetSceneAreaRsp.java new file mode 100644 index 00000000..8a8bdca3 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetSceneAreaRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CityInfoOuterClass.CityInfo; +import emu.grasscutter.net.proto.GetSceneAreaRspOuterClass.GetSceneAreaRsp; + +public class PacketGetSceneAreaRsp extends GenshinPacket { + + public PacketGetSceneAreaRsp(int sceneId) { + super(PacketOpcodes.GetSceneAreaRsp); + + this.buildHeader(0); + + GetSceneAreaRsp p = GetSceneAreaRsp.newBuilder() + .setSceneId(3) + .addAllAreaIdList(Arrays.stream(new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,18,19}).boxed().collect(Collectors.toList())) + .addCityInfoList(CityInfo.newBuilder().setCityId(1).setLevel(1).build()) + .addCityInfoList(CityInfo.newBuilder().setCityId(2).setLevel(1).build()) + .addCityInfoList(CityInfo.newBuilder().setCityId(3).setLevel(1).build()) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java new file mode 100644 index 00000000..73807bed --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetScenePointRspOuterClass.GetScenePointRsp; + +public class PacketGetScenePointRsp extends GenshinPacket { + + public PacketGetScenePointRsp(int sceneId) { + super(PacketOpcodes.GetScenePointRsp); + + GetScenePointRsp.Builder p = GetScenePointRsp.newBuilder() + .setSceneId(sceneId); + + for (int i = 1; i < 1000; i++) { + p.addUnlockedPointList(i); + } + + for (int i = 1; i < 9; i++) { + p.addUnlockAreaList(i); + } + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java new file mode 100644 index 00000000..d80d445d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; +import emu.grasscutter.net.proto.ShopOuterClass.Shop; + +public class PacketGetShopRsp extends GenshinPacket { + + public PacketGetShopRsp(int shopType) { + super(PacketOpcodes.GetShopRsp); + + GetShopRsp proto = GetShopRsp.newBuilder() + .setShop(Shop.newBuilder().setShopType(shopType)) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopmallDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopmallDataRsp.java new file mode 100644 index 00000000..38b44b5b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetShopmallDataRsp.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetShopmallDataRspOuterClass.GetShopmallDataRsp; + +public class PacketGetShopmallDataRsp extends GenshinPacket { + + public PacketGetShopmallDataRsp() { + super(PacketOpcodes.GetShopmallDataRsp); + + GetShopmallDataRsp proto = GetShopmallDataRsp.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetWorldMpInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWorldMpInfoRsp.java new file mode 100644 index 00000000..1b06c2cf --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWorldMpInfoRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetWorldMpInfoRspOuterClass.GetWorldMpInfoRsp; + +public class PacketGetWorldMpInfoRsp extends GenshinPacket { + + public PacketGetWorldMpInfoRsp(World world) { + super(PacketOpcodes.GetWorldMpInfoRsp); + + GetWorldMpInfoRsp proto = GetWorldMpInfoRsp.newBuilder() + .setIsInMpMode(world.isMultiplayer()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketH5ActivityIdsNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketH5ActivityIdsNotify.java new file mode 100644 index 00000000..9ce59137 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketH5ActivityIdsNotify.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.H5ActivityIdsNotifyOuterClass.H5ActivityIdsNotify; + +public class PacketH5ActivityIdsNotify extends GenshinPacket { + + public PacketH5ActivityIdsNotify() { + super(PacketOpcodes.H5ActivityIdsNotify); + + H5ActivityIdsNotify proto = H5ActivityIdsNotify.newBuilder() + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHostPlayerNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHostPlayerNotify.java new file mode 100644 index 00000000..b291074d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHostPlayerNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HostPlayerNotifyOuterClass.HostPlayerNotify; + +public class PacketHostPlayerNotify extends GenshinPacket { + + public PacketHostPlayerNotify(World world) { + super(PacketOpcodes.HostPlayerNotify); + + HostPlayerNotify proto = HostPlayerNotify.newBuilder() + .setHostUid(world.getHost().getId()) + .setHostPeerId(world.getHost().getPeerId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketItemAddHintNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketItemAddHintNotify.java new file mode 100644 index 00000000..598b9a0f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketItemAddHintNotify.java @@ -0,0 +1,36 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ItemAddHintNotifyOuterClass.ItemAddHintNotify; + +public class PacketItemAddHintNotify extends GenshinPacket { + + public PacketItemAddHintNotify(GenshinItem item, ActionReason reason) { + super(PacketOpcodes.ItemAddHintNotify); + + ItemAddHintNotify proto = ItemAddHintNotify.newBuilder() + .addItemList(item.toItemHintProto()) + .setReason(reason.getValue()) + .build(); + + this.setData(proto); + } + + public PacketItemAddHintNotify(List items, ActionReason reason) { + super(PacketOpcodes.ItemAddHintNotify); + + ItemAddHintNotify.Builder proto = ItemAddHintNotify.newBuilder() + .setReason(reason.getValue()); + + for (GenshinItem item : items) { + proto.addItemList(item.toItemHintProto()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java new file mode 100644 index 00000000..1f26717a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.LifeStateChangeNotifyOuterClass.LifeStateChangeNotify; + +public class PacketLifeStateChangeNotify extends GenshinPacket { + public PacketLifeStateChangeNotify(GenshinEntity attacker, GenshinEntity target, LifeState lifeState) { + super(PacketOpcodes.LifeStateChangeNotify); + + LifeStateChangeNotify proto = LifeStateChangeNotify.newBuilder() + .setEntityId(target.getId()) + .setLifeState(lifeState.getValue()) + .setSourceEntityId(attacker.getId()) + .build(); + + this.setData(proto); + } + public PacketLifeStateChangeNotify(int attackerId, GenshinEntity target, LifeState lifeState) { + super(PacketOpcodes.LifeStateChangeNotify); + + LifeStateChangeNotify proto = LifeStateChangeNotify.newBuilder() + .setEntityId(target.getId()) + .setLifeState(lifeState.getValue()) + .setSourceEntityId(attackerId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java new file mode 100644 index 00000000..c0b5fd38 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.props.OpenState; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.OpenStateUpdateNotifyOuterClass.OpenStateUpdateNotify; + +public class PacketOpenStateUpdateNotify extends GenshinPacket { + + public PacketOpenStateUpdateNotify() { + super(PacketOpcodes.OpenStateUpdateNotify); + + OpenStateUpdateNotify.Builder proto = OpenStateUpdateNotify.newBuilder(); + + for (OpenState type : OpenState.values()) { + if (type.getValue() > 0) { + proto.putOpenStateMap(type.getValue(), 1); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java new file mode 100644 index 00000000..029b0fc7 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java @@ -0,0 +1,13 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; + +public class PacketPathfindingEnterSceneRsp extends GenshinPacket { + + public PacketPathfindingEnterSceneRsp(int clientSequence) { + super(PacketOpcodes.PathfindingEnterSceneRsp); + + this.buildHeader(clientSequence); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPingRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPingRsp.java new file mode 100644 index 00000000..47151bf0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPingRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PingRspOuterClass.PingRsp; + +public class PacketPingRsp extends GenshinPacket { + + public PacketPingRsp(int clientSeq, int time) { + super(PacketOpcodes.PingRsp, clientSeq); + + PingRsp p = PingRsp.newBuilder() + .setClientTime(time) + .build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpNotify.java new file mode 100644 index 00000000..7d064aa9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpNotifyOuterClass.PlayerApplyEnterMpNotify; + +public class PacketPlayerApplyEnterMpNotify extends GenshinPacket { + + public PacketPlayerApplyEnterMpNotify(GenshinPlayer srcPlayer) { + super(PacketOpcodes.PlayerApplyEnterMpNotify); + + PlayerApplyEnterMpNotify proto = PlayerApplyEnterMpNotify.newBuilder() + .setSrcPlayerInfo(srcPlayer.getOnlinePlayerInfo()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultNotify.java new file mode 100644 index 00000000..2e94cc4d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultNotify.java @@ -0,0 +1,36 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason; +import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass.PlayerApplyEnterMpResultNotify; + +public class PacketPlayerApplyEnterMpResultNotify extends GenshinPacket { + + public PacketPlayerApplyEnterMpResultNotify(GenshinPlayer target, boolean isAgreed, PlayerApplyEnterMpReason reason) { + super(PacketOpcodes.PlayerApplyEnterMpResultNotify); + + PlayerApplyEnterMpResultNotify proto = PlayerApplyEnterMpResultNotify.newBuilder() + .setTargetUid(target.getId()) + .setTargetNickname(target.getNickname()) + .setIsAgreed(isAgreed) + .setReason(reason) + .build(); + + this.setData(proto); + } + + public PacketPlayerApplyEnterMpResultNotify(int targetId, String targetName, boolean isAgreed, PlayerApplyEnterMpReason reason) { + super(PacketOpcodes.PlayerApplyEnterMpResultNotify); + + PlayerApplyEnterMpResultNotify proto = PlayerApplyEnterMpResultNotify.newBuilder() + .setTargetUid(targetId) + .setTargetNickname(targetName) + .setIsAgreed(isAgreed) + .setReason(reason) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultRsp.java new file mode 100644 index 00000000..7fa4baa5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpResultRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpResultRspOuterClass.PlayerApplyEnterMpResultRsp; + +public class PacketPlayerApplyEnterMpResultRsp extends GenshinPacket { + + public PacketPlayerApplyEnterMpResultRsp(int applyUid, boolean isAgreed) { + super(PacketOpcodes.PlayerApplyEnterMpResultRsp); + + PlayerApplyEnterMpResultRsp proto = PlayerApplyEnterMpResultRsp.newBuilder() + .setApplyUid(applyUid) + .setIsAgreed(isAgreed) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpRsp.java new file mode 100644 index 00000000..71174371 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerApplyEnterMpRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerApplyEnterMpRspOuterClass.PlayerApplyEnterMpRsp; + +public class PacketPlayerApplyEnterMpRsp extends GenshinPacket { + + public PacketPlayerApplyEnterMpRsp(int targetUid) { + super(PacketOpcodes.PlayerApplyEnterMpRsp); + + PlayerApplyEnterMpRsp proto = PlayerApplyEnterMpRsp.newBuilder() + .setTargetUid(targetUid) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatNotify.java new file mode 100644 index 00000000..482a00ae --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatNotify.java @@ -0,0 +1,62 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; +import emu.grasscutter.net.proto.PlayerChatNotifyOuterClass.PlayerChatNotify; +import emu.grasscutter.net.proto.SystemHintOuterClass.SystemHint; + +public class PacketPlayerChatNotify extends GenshinPacket { + + public PacketPlayerChatNotify(GenshinPlayer sender, int channelId, String message) { + super(PacketOpcodes.PlayerChatNotify); + + ChatInfo info = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(sender.getId()) + .setText(message) + .build(); + + PlayerChatNotify proto = PlayerChatNotify.newBuilder() + .setChannelId(channelId) + .setChatInfo(info) + .build(); + + this.setData(proto); + } + + public PacketPlayerChatNotify(GenshinPlayer sender, int channelId, int emote) { + super(PacketOpcodes.PlayerChatNotify); + + ChatInfo info = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(sender.getId()) + .setIcon(emote) + .build(); + + PlayerChatNotify proto = PlayerChatNotify.newBuilder() + .setChannelId(channelId) + .setChatInfo(info) + .build(); + + this.setData(proto); + } + + public PacketPlayerChatNotify(GenshinPlayer sender, int channelId, SystemHint systemHint) { + super(PacketOpcodes.PlayerChatNotify); + + ChatInfo info = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(sender.getId()) + .setSystemHint(systemHint) + .build(); + + PlayerChatNotify proto = PlayerChatNotify.newBuilder() + .setChannelId(channelId) + .setChatInfo(info) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatRsp.java new file mode 100644 index 00000000..83b51a2a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerChatRsp.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerChatRspOuterClass.PlayerChatRsp; + +public class PacketPlayerChatRsp extends GenshinPacket { + + public PacketPlayerChatRsp() { + super(PacketOpcodes.PlayerChatRsp); + + PlayerChatRsp proto = PlayerChatRsp.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerDataNotify.java new file mode 100644 index 00000000..51b043ab --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerDataNotify.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerDataNotifyOuterClass.PlayerDataNotify; +import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; + +public class PacketPlayerDataNotify extends GenshinPacket { + + public PacketPlayerDataNotify(GenshinPlayer player) { + super(PacketOpcodes.PlayerDataNotify, 2); + + PlayerDataNotify.Builder p = PlayerDataNotify.newBuilder() + .setNickName(player.getNickname()) + .setClientTime(System.currentTimeMillis()) + .setIsFirstLoginToday(true) + .setRegionId(player.getRegionId()); + + player.getProperties().forEach((key, value) -> { + p.putPropMap(key, PropValue.newBuilder().setType(key).setIval(value).setVal(value).build()); + }); + + this.setData(p.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java new file mode 100644 index 00000000..e47021a0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneInfoNotify.java @@ -0,0 +1,56 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AvatarEnterSceneInfoOuterClass.AvatarEnterSceneInfo; +import emu.grasscutter.net.proto.MPLevelEntityInfoOuterClass.MPLevelEntityInfo; +import emu.grasscutter.net.proto.PlayerEnterSceneInfoNotifyOuterClass.PlayerEnterSceneInfoNotify; +import emu.grasscutter.net.proto.TeamEnterSceneInfoOuterClass.TeamEnterSceneInfo; + +public class PacketPlayerEnterSceneInfoNotify extends GenshinPacket { + + public PacketPlayerEnterSceneInfoNotify(GenshinPlayer player) { + super(PacketOpcodes.PlayerEnterSceneInfoNotify); + + AbilitySyncStateInfo empty = AbilitySyncStateInfo.newBuilder().build(); + + PlayerEnterSceneInfoNotify.Builder proto = PlayerEnterSceneInfoNotify.newBuilder() + .setCurAvatarEntityId(player.getTeamManager().getCurrentAvatarEntity().getId()) + .setEnterSceneToken(player.getEnterSceneToken()); + + proto.setTeamEnterInfo( + TeamEnterSceneInfo.newBuilder() + .setTeamEntityId(player.getTeamManager().getEntityId()) // 150995833 + .setTeamAbilityInfo(empty) + .setUnk(empty) + ); + proto.setMpLevelEntityInfo( + MPLevelEntityInfo.newBuilder() + .setEntityId(player.getWorld().getLevelEntityId()) // 184550274 + .setAuthorityPeerId(player.getWorld().getHostPeerId()) + .setAbilityInfo(empty) + ); + + for (EntityAvatar avatarEntity : player.getTeamManager().getActiveTeam()) { + GenshinItem weapon = avatarEntity.getAvatar().getWeapon(); + long weaponGuid = weapon != null ? weapon.getGuid() : 0; + + AvatarEnterSceneInfo avatarInfo = AvatarEnterSceneInfo.newBuilder() + .setAvatarGuid(avatarEntity.getAvatar().getGuid()) + .setAvatarEntityId(avatarEntity.getId()) + .setWeaponGuid(weaponGuid) + .setWeaponEntityId(avatarEntity.getWeaponEntityId()) + .setAvatarAbilityInfo(empty) + .setWeaponAbilityInfo(empty) + .build(); + + proto.addAvatarEnterInfo(avatarInfo); + } + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java new file mode 100644 index 00000000..a40663b1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java @@ -0,0 +1,74 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.GenshinPlayer.SceneLoadState; +import emu.grasscutter.game.props.EnterReason; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; +import emu.grasscutter.net.proto.PlayerEnterSceneNotifyOuterClass.PlayerEnterSceneNotify; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.Utils; + +public class PacketPlayerEnterSceneNotify extends GenshinPacket { + + // Login + public PacketPlayerEnterSceneNotify(GenshinPlayer player) { + super(PacketOpcodes.PlayerEnterSceneNotify); + + player.setSceneLoadState(SceneLoadState.LOADING); + player.setEnterSceneToken(Utils.randomRange(1000, 99999)); + + PlayerEnterSceneNotify proto = PlayerEnterSceneNotify.newBuilder() + .setSceneId(player.getSceneId()) + .setPos(player.getPos().toProto()) + .setSceneBeginTime(System.currentTimeMillis()) + .setType(EnterType.EnterSelf) + .setTargetUid(player.getId()) + .setEnterSceneToken(player.getEnterSceneToken()) + .setWorldLevel(player.getWorldLevel()) + .setEnterReason(EnterReason.Login.getValue()) + .setIsFirstLoginEnterScene(player.isFirstLoginEnterScene()) + .addSceneTagIdList(102) + .addSceneTagIdList(107) + .addSceneTagIdList(113) + .addSceneTagIdList(117) + .setUnk1(1) + .setUnk2("3-" + player.getId() + "-" + (int) (System.currentTimeMillis() / 1000) + "-" + 18402) + .build(); + + this.setData(proto); + } + + public PacketPlayerEnterSceneNotify(GenshinPlayer player, EnterType type, EnterReason reason, int newScene, Position newPos) { + this(player, player, type, reason, newScene, newPos); + } + + // Teleport or go somewhere + public PacketPlayerEnterSceneNotify(GenshinPlayer player, GenshinPlayer target, EnterType type, EnterReason reason, int newScene, Position newPos) { + super(PacketOpcodes.PlayerEnterSceneNotify); + + player.setEnterSceneToken(Utils.randomRange(1000, 99999)); + + PlayerEnterSceneNotify proto = PlayerEnterSceneNotify.newBuilder() + .setPrevSceneId(player.getSceneId()) + .setPrevPos(player.getPos().toProto()) + .setSceneId(newScene) + .setPos(newPos.toProto()) + .setSceneBeginTime(System.currentTimeMillis()) + .setType(EnterType.EnterSelf) + .setTargetUid(target.getId()) + .setEnterSceneToken(player.getEnterSceneToken()) + .setWorldLevel(target.getWorld().getWorldLevel()) + .setEnterReason(reason.getValue()) + .addSceneTagIdList(102) + .addSceneTagIdList(107) + .addSceneTagIdList(113) + .addSceneTagIdList(117) + .setUnk1(1) + .setUnk2(newScene + "-" + target.getId() + "-" + (int) (System.currentTimeMillis() / 1000) + "-" + 18402) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java new file mode 100644 index 00000000..08502cdf --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerGameTimeNotifyOuterClass.PlayerGameTimeNotify; + +public class PacketPlayerGameTimeNotify extends GenshinPacket { + + public PacketPlayerGameTimeNotify(World world, GenshinPlayer player) { + super(PacketOpcodes.PlayerGameTimeNotify); + + PlayerGameTimeNotify proto = PlayerGameTimeNotify.newBuilder() + .setGameTime(world.getTime()) + .setUid(player.getId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGetForceQuitBanInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGetForceQuitBanInfoRsp.java new file mode 100644 index 00000000..acc03081 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGetForceQuitBanInfoRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerGetForceQuitBanInfoRspOuterClass.PlayerGetForceQuitBanInfoRsp; + +public class PacketPlayerGetForceQuitBanInfoRsp extends GenshinPacket { + + public PacketPlayerGetForceQuitBanInfoRsp(int retcode) { + super(PacketOpcodes.PlayerGetForceQuitBanInfoRsp); + + PlayerGetForceQuitBanInfoRsp proto = PlayerGetForceQuitBanInfoRsp.newBuilder() + .setRetcode(retcode) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java new file mode 100644 index 00000000..0d0871ca --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp; +import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; +import emu.grasscutter.server.game.GameSession; + +public class PacketPlayerLoginRsp extends GenshinPacket { + + public PacketPlayerLoginRsp(GameSession session) { + super(PacketOpcodes.PlayerLoginRsp, 1); + + this.setUseDispatchKey(true); + + RegionInfo info = Grasscutter.getDispatchServer().getCurrRegion().getRegionInfo(); + + PlayerLoginRsp p = PlayerLoginRsp.newBuilder() + .setIsUseAbilityHash(true) // true + .setAbilityHashCode(1844674) // 1844674 + .setGameBiz("hk4e_global") + .setClientDataVersion(info.getClientDataVersion()) + .setClientSilenceDataVersion(info.getClientSilenceDataVersion()) + .setClientMd5(info.getClientDataMd5()) + .setClientSilenceMd5(info.getClientSilenceDataMd5()) + .setResVersionConfig(info.getConfig()) + .setClientVersionSuffix(info.getClientVersionSuffix()) + .setClientSilenceVersionSuffix(info.getClientSilenceVersionSuffix()) + .setIsScOpen(false) + //.setScInfo(ByteString.copyFrom(new byte[] {})) + .setRegisterCps("mihoyo") + .setCountryCode("US") + .build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerPropNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerPropNotify.java new file mode 100644 index 00000000..2e2812eb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerPropNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerPropNotifyOuterClass.PlayerPropNotify; +import emu.grasscutter.utils.ProtoHelper; + +public class PacketPlayerPropNotify extends GenshinPacket { + + public PacketPlayerPropNotify(GenshinPlayer player, PlayerProperty prop) { + super(PacketOpcodes.PlayerPropNotify); + + this.buildHeader(0); + + PlayerPropNotify proto = PlayerPropNotify.newBuilder() + .putPropMap(prop.getId(), ProtoHelper.newPropValue(prop, player.getProperty(prop))) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerSetPauseRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerSetPauseRsp.java new file mode 100644 index 00000000..e199381f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerSetPauseRsp.java @@ -0,0 +1,13 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; + +public class PacketPlayerSetPauseRsp extends GenshinPacket { + + public PacketPlayerSetPauseRsp(int clientSequence) { + super(PacketOpcodes.PlayerSetPauseRsp); + + this.buildHeader(clientSequence); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java new file mode 100644 index 00000000..45a13325 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ItemOuterClass.Item; +import emu.grasscutter.net.proto.PlayerStoreNotifyOuterClass.PlayerStoreNotify; +import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; + +public class PacketPlayerStoreNotify extends GenshinPacket { + + public PacketPlayerStoreNotify(GenshinPlayer player) { + super(PacketOpcodes.PlayerStoreNotify); + + this.buildHeader(2); + + PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder() + .setStoreType(StoreType.StorePack) + .setWeightLimit(GenshinConstants.LIMIT_ALL); + + for (GenshinItem item : player.getInventory()) { + Item itemProto = item.toProto(); + p.addItemList(itemProto); + } + + this.setData(p.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerTimeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerTimeNotify.java new file mode 100644 index 00000000..ad9a869c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerTimeNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerTimeNotifyOuterClass.PlayerTimeNotify; + +public class PacketPlayerTimeNotify extends GenshinPacket { + + public PacketPlayerTimeNotify(GenshinPlayer player) { + super(PacketOpcodes.PlayerTimeNotify); + + PlayerTimeNotify proto = PlayerTimeNotify.newBuilder() + .setIsPaused(player.isPaused()) + .setPlayerTime(player.getClientTime()) + .setServerTime(System.currentTimeMillis()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPostEnterSceneRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPostEnterSceneRsp.java new file mode 100644 index 00000000..be8dede9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPostEnterSceneRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PostEnterSceneRspOuterClass.PostEnterSceneRsp; + +public class PacketPostEnterSceneRsp extends GenshinPacket { + + public PacketPostEnterSceneRsp(GenshinPlayer player) { + super(PacketOpcodes.PostEnterSceneRsp); + + PostEnterSceneRsp p = PostEnterSceneRsp.newBuilder() + .setEnterSceneToken(player.getEnterSceneToken()) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPrivateChatNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPrivateChatNotify.java new file mode 100644 index 00000000..60e08827 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPrivateChatNotify.java @@ -0,0 +1,42 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; +import emu.grasscutter.net.proto.PrivateChatNotifyOuterClass.PrivateChatNotify; + +public class PacketPrivateChatNotify extends GenshinPacket { + public PacketPrivateChatNotify(int senderId, int recvId, String message) { + super(PacketOpcodes.PrivateChatNotify); + + ChatInfo info = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(senderId) + .setToUid(recvId) + .setText(message) + .build(); + + PrivateChatNotify proto = PrivateChatNotify.newBuilder() + .setChatInfo(info) + .build(); + + this.setData(proto); + } + + public PacketPrivateChatNotify(int senderId, int recvId, int emote) { + super(PacketOpcodes.PrivateChatNotify); + + ChatInfo info = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(senderId) + .setToUid(recvId) + .setIcon(emote) + .build(); + + PrivateChatNotify proto = PrivateChatNotify.newBuilder() + .setChatInfo(info) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillChangeNotify.java new file mode 100644 index 00000000..b089e5c8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillChangeNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ProudSkillChangeNotifyOuterClass.ProudSkillChangeNotify; + +public class PacketProudSkillChangeNotify extends GenshinPacket { + + public PacketProudSkillChangeNotify(GenshinAvatar avatar) { + super(PacketOpcodes.ProudSkillChangeNotify); + + ProudSkillChangeNotify proto = ProudSkillChangeNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setEntityId(avatar.getEntityId()) + .setSkillDepotId(avatar.getSkillDepotId()) + .addAllProudSkillList(avatar.getProudSkillList()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillExtraLevelNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillExtraLevelNotify.java new file mode 100644 index 00000000..127a0ec4 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketProudSkillExtraLevelNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ProudSkillExtraLevelNotifyOuterClass.ProudSkillExtraLevelNotify; + +public class PacketProudSkillExtraLevelNotify extends GenshinPacket { + + public PacketProudSkillExtraLevelNotify(GenshinAvatar avatar, int talentIndex) { + super(PacketOpcodes.ProudSkillExtraLevelNotify); + + ProudSkillExtraLevelNotify proto = ProudSkillExtraLevelNotify.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setTalentType(3) // Talent type = 3 + .setTalentIndex(talentIndex) + .setExtraLevel(3) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPullPrivateChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPullPrivateChatRsp.java new file mode 100644 index 00000000..6d5e829c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPullPrivateChatRsp.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PullPrivateChatRspOuterClass.PullPrivateChatRsp; + +public class PacketPullPrivateChatRsp extends GenshinPacket { + + public PacketPullPrivateChatRsp() { + super(PacketOpcodes.PullPrivateChatRsp); + + PullPrivateChatRsp proto = PullPrivateChatRsp.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java new file mode 100644 index 00000000..34ca8b3b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java @@ -0,0 +1,44 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.Config.ServerOptions; +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; +import emu.grasscutter.net.proto.PullRecentChatRspOuterClass.PullRecentChatRsp; +import emu.grasscutter.utils.Utils; + +public class PacketPullRecentChatRsp extends GenshinPacket { + public PacketPullRecentChatRsp(GenshinPlayer player) { + super(PacketOpcodes.PullRecentChatRsp); + + ServerOptions serverOptions = Grasscutter.getConfig().getServerOptions(); + PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder(); + + if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) { + ChatInfo welcomeEmote = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(GenshinConstants.SERVER_CONSOLE_UID) + .setToUid(player.getId()) + .setIcon(serverOptions.WelcomeEmotes[Utils.randomRange(0, serverOptions.WelcomeEmotes.length - 1)]) + .build(); + + proto.addChatInfo(welcomeEmote); + } + + if (serverOptions.WelcomeMotd != null && serverOptions.WelcomeMotd.length() > 0) { + ChatInfo welcomeMotd = ChatInfo.newBuilder() + .setTime((int) (System.currentTimeMillis() / 1000)) + .setUid(GenshinConstants.SERVER_CONSOLE_UID) + .setToUid(player.getId()) + .setText(Grasscutter.getConfig().getServerOptions().WelcomeMotd) + .build(); + + proto.addChatInfo(welcomeMotd); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketReliquaryUpgradeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketReliquaryUpgradeRsp.java new file mode 100644 index 00000000..34c9c3d8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketReliquaryUpgradeRsp.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ReliquaryUpgradeRspOuterClass.ReliquaryUpgradeRsp; + +public class PacketReliquaryUpgradeRsp extends GenshinPacket { + + public PacketReliquaryUpgradeRsp(GenshinItem relic, int rate, int oldLevel, List oldAppendPropIdList) { + super(PacketOpcodes.ReliquaryUpgradeRsp); + + ReliquaryUpgradeRsp proto = ReliquaryUpgradeRsp.newBuilder() + .setTargetReliquaryGuid(relic.getGuid()) + .setOldLevel(oldLevel) + .setCurLevel(relic.getLevel()) + .setPowerUpRate(rate) + .addAllOldAppendPropList(oldAppendPropIdList) + .addAllCurAppendPropList(relic.getAppendPropIdList()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java new file mode 100644 index 00000000..0cf62297 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneAreaWeatherNotifyOuterClass.SceneAreaWeatherNotify; + +public class PacketSceneAreaWeatherNotify extends GenshinPacket { + + public PacketSceneAreaWeatherNotify(World world, GenshinPlayer player) { + super(PacketOpcodes.SceneAreaWeatherNotify); + + SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder() + .setWeatherAreaId(1) + .setClimateType(world.getClimate().getValue()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java new file mode 100644 index 00000000..3b8a3902 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java @@ -0,0 +1,49 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collection; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneEntityAppearNotifyOuterClass.SceneEntityAppearNotify; +import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; + +public class PacketSceneEntityAppearNotify extends GenshinPacket { + + public PacketSceneEntityAppearNotify(GenshinEntity entity) { + super(PacketOpcodes.SceneEntityAppearNotify, true); + + SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder() + .setAppearType(VisionType.VisionBorn) + .addEntityList(entity.toProto()); + + this.setData(proto.build()); + } + + public PacketSceneEntityAppearNotify(GenshinEntity entity, VisionType vision, int param) { + super(PacketOpcodes.SceneEntityAppearNotify, true); + + SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder() + .setAppearType(vision) + .setParam(param) + .addEntityList(entity.toProto()); + + this.setData(proto.build()); + } + + public PacketSceneEntityAppearNotify(GenshinPlayer player) { + this(player.getTeamManager().getCurrentAvatarEntity()); + } + + public PacketSceneEntityAppearNotify(Collection entities, VisionType visionType) { + super(PacketOpcodes.SceneEntityAppearNotify, true); + + SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder() + .setAppearType(visionType); + + entities.forEach(e -> proto.addEntityList(e.toProto())); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityDisappearNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityDisappearNotify.java new file mode 100644 index 00000000..93d91063 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityDisappearNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneEntityDisappearNotifyOuterClass.SceneEntityDisappearNotify; +import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; + +public class PacketSceneEntityDisappearNotify extends GenshinPacket { + + public PacketSceneEntityDisappearNotify(GenshinEntity entity, VisionType disappearType) { + super(PacketOpcodes.SceneEntityDisappearNotify); + + SceneEntityDisappearNotify proto = SceneEntityDisappearNotify.newBuilder() + .setDisappearType(disappearType) + .addEntityList(entity.getId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityMoveNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityMoveNotify.java new file mode 100644 index 00000000..c3230f4d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityMoveNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +import emu.grasscutter.net.proto.SceneEntityMoveNotifyOuterClass.SceneEntityMoveNotify; + +public class PacketSceneEntityMoveNotify extends GenshinPacket { + + public PacketSceneEntityMoveNotify(EntityMoveInfo moveInfo) { + super(PacketOpcodes.SceneEntityMoveNotify, true); + + SceneEntityMoveNotify proto = SceneEntityMoveNotify.newBuilder() + .setMotionInfo(moveInfo.getMotionInfo()) + .setEntityId(moveInfo.getEntityId()) + .setSceneTime(moveInfo.getSceneTime()) + .setReliableSeq(moveInfo.getReliableSeq()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneInitFinishRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneInitFinishRsp.java new file mode 100644 index 00000000..78661f01 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneInitFinishRsp.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneInitFinishRspOuterClass.SceneInitFinishRsp; + +public class PacketSceneInitFinishRsp extends GenshinPacket { + + public PacketSceneInitFinishRsp(GenshinPlayer player) { + super(PacketOpcodes.SceneInitFinishRsp, 11); + + SceneInitFinishRsp p = SceneInitFinishRsp.newBuilder().setEnterSceneToken(player.getEnterSceneToken()).build(); + + this.setData(p.toByteArray()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneKickPlayerRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneKickPlayerRsp.java new file mode 100644 index 00000000..7f0a6a12 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneKickPlayerRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneKickPlayerRspOuterClass.SceneKickPlayerRsp; + +public class PacketSceneKickPlayerRsp extends GenshinPacket { + + public PacketSceneKickPlayerRsp(int targetUid) { + super(PacketOpcodes.SceneKickPlayerRsp); + + SceneKickPlayerRsp proto = SceneKickPlayerRsp.newBuilder() + .setTargetUid(targetUid) + .build(); + + this.setData(proto); + } + + public PacketSceneKickPlayerRsp() { + super(PacketOpcodes.SceneKickPlayerRsp); + + SceneKickPlayerRsp proto = SceneKickPlayerRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerInfoNotify.java new file mode 100644 index 00000000..8e683016 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerInfoNotify.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ScenePlayerInfoNotifyOuterClass.ScenePlayerInfoNotify; +import emu.grasscutter.net.proto.ScenePlayerInfoOuterClass.ScenePlayerInfo; + +public class PacketScenePlayerInfoNotify extends GenshinPacket { + + public PacketScenePlayerInfoNotify(World world) { + super(PacketOpcodes.ScenePlayerInfoNotify); + + ScenePlayerInfoNotify.Builder proto = ScenePlayerInfoNotify.newBuilder(); + + for (int i = 0; i < world.getPlayers().size(); i++) { + GenshinPlayer p = world.getPlayers().get(i); + + ScenePlayerInfo pInfo = ScenePlayerInfo.newBuilder() + .setUid(p.getId()) + .setPeerId(p.getPeerId()) + .setName(p.getNickname()) + .setSceneId(world.getSceneId()) + .setOnlinePlayerInfo(p.getOnlinePlayerInfo()) + .build(); + + proto.addPlayerInfoList(pInfo); + } + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java new file mode 100644 index 00000000..f6fa9b8f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ScenePlayerLocationNotifyOuterClass.ScenePlayerLocationNotify; + +public class PacketScenePlayerLocationNotify extends GenshinPacket { + + public PacketScenePlayerLocationNotify(GenshinPlayer player) { + super(PacketOpcodes.ScenePlayerLocationNotify); + + ScenePlayerLocationNotify.Builder proto = ScenePlayerLocationNotify.newBuilder() + .setSceneId(player.getSceneId()); + + for (GenshinPlayer p : player.getWorld().getPlayers()) { + proto.addPlayerLocList(p.getPlayerLocationInfo()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTeamUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTeamUpdateNotify.java new file mode 100644 index 00000000..1dac583c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTeamUpdateNotify.java @@ -0,0 +1,46 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.SceneTeamAvatarOuterClass.SceneTeamAvatar; +import emu.grasscutter.net.proto.SceneTeamUpdateNotifyOuterClass.SceneTeamUpdateNotify; + +public class PacketSceneTeamUpdateNotify extends GenshinPacket { + + public PacketSceneTeamUpdateNotify(GenshinPlayer player) { + super(PacketOpcodes.SceneTeamUpdateNotify); + + SceneTeamUpdateNotify.Builder proto = SceneTeamUpdateNotify.newBuilder() + .setIsInMp(player.getWorld().isMultiplayer()); + + for (GenshinPlayer p : player.getWorld().getPlayers()) { + for (EntityAvatar entityAvatar : p.getTeamManager().getActiveTeam()) { + SceneTeamAvatar.Builder avatarProto = SceneTeamAvatar.newBuilder() + .setPlayerId(p.getId()) + .setAvatarGuid(entityAvatar.getAvatar().getGuid()) + .setSceneId(p.getSceneId()) + .setEntityId(entityAvatar.getId()) + .setSceneEntityInfo(entityAvatar.toProto()) + .setWeaponGuid(entityAvatar.getAvatar().getWeapon().getGuid()) + .setWeaponEntityId(entityAvatar.getWeaponEntityId()) + .setIsPlayerCurAvatar(p.getTeamManager().getCurrentAvatarEntity() == entityAvatar) + .setIsOnScene(p.getTeamManager().getCurrentAvatarEntity() == entityAvatar) + .setAvatarAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setWeaponAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setAbilityControlBlock(entityAvatar.getAbilityControlBlock()); + + if (player.getWorld().isMultiplayer()) { + avatarProto.setAvatarInfo(entityAvatar.getAvatar().toProto()); + avatarProto.setSceneAvatarInfo(entityAvatar.getSceneAvatarInfo()); // why mihoyo... + } + + proto.addSceneTeamAvatarList(avatarProto); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTimeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTimeNotify.java new file mode 100644 index 00000000..5af88cf2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTimeNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SceneTimeNotifyOuterClass.SceneTimeNotify; + +public class PacketSceneTimeNotify extends GenshinPacket { + + public PacketSceneTimeNotify(GenshinPlayer player) { + super(PacketOpcodes.SceneTimeNotify); + + SceneTimeNotify proto = SceneTimeNotify.newBuilder() + .setSceneId(player.getSceneId()) + .setSceneTime(0) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerTimeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerTimeNotify.java new file mode 100644 index 00000000..a58b634f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerTimeNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ServerTimeNotifyOuterClass.ServerTimeNotify; + +public class PacketServerTimeNotify extends GenshinPacket { + + public PacketServerTimeNotify() { + super(PacketOpcodes.ServerTimeNotify); + + ServerTimeNotify proto = ServerTimeNotify.newBuilder() + .setServerTime(System.currentTimeMillis()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetEquipLockStateRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetEquipLockStateRsp.java new file mode 100644 index 00000000..7614c449 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetEquipLockStateRsp.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetEquipLockStateRspOuterClass.SetEquipLockStateRsp; + +public class PacketSetEquipLockStateRsp extends GenshinPacket { + + public PacketSetEquipLockStateRsp(GenshinItem equip) { + super(PacketOpcodes.SetEquipLockStateRsp); + + this.buildHeader(0); + + SetEquipLockStateRsp proto = SetEquipLockStateRsp.newBuilder() + .setTargetEquipGuid(equip.getGuid()) + .setIsLocked(equip.isLocked()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetNameCardRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetNameCardRsp.java new file mode 100644 index 00000000..b3c77c33 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetNameCardRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetNameCardRspOuterClass.SetNameCardRsp; + +public class PacketSetNameCardRsp extends GenshinPacket { + + public PacketSetNameCardRsp(int nameCardId) { + super(PacketOpcodes.SetNameCardRsp); + + SetNameCardRsp proto = SetNameCardRsp.newBuilder() + .setNameCardId(nameCardId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerHeadImageRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerHeadImageRsp.java new file mode 100644 index 00000000..148c536f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerHeadImageRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage; +import emu.grasscutter.net.proto.SetPlayerHeadImageRspOuterClass.SetPlayerHeadImageRsp; + +public class PacketSetPlayerHeadImageRsp extends GenshinPacket { + + public PacketSetPlayerHeadImageRsp(GenshinPlayer player) { + super(PacketOpcodes.SetPlayerHeadImageRsp); + + SetPlayerHeadImageRsp proto = SetPlayerHeadImageRsp.newBuilder() + .setAvatar(HeadImage.newBuilder().setAvatarId(player.getHeadImage())) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerNameRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerNameRsp.java new file mode 100644 index 00000000..a40b5115 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerNameRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerNameRspOuterClass.SetPlayerNameRsp; + +public class PacketSetPlayerNameRsp extends GenshinPacket { + + public PacketSetPlayerNameRsp(GenshinPlayer player) { + super(PacketOpcodes.SetPlayerNameRsp); + + SetPlayerNameRsp proto = SetPlayerNameRsp.newBuilder() + .setNickName(player.getNickname()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerSignatureRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerSignatureRsp.java new file mode 100644 index 00000000..98ee97b6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetPlayerSignatureRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetPlayerSignatureRspOuterClass.SetPlayerSignatureRsp; + +public class PacketSetPlayerSignatureRsp extends GenshinPacket { + + public PacketSetPlayerSignatureRsp(GenshinPlayer player) { + super(PacketOpcodes.SetPlayerSignatureRsp); + + SetPlayerSignatureRsp proto = SetPlayerSignatureRsp.newBuilder() + .setSignature(player.getSignature()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpAvatarTeamRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpAvatarTeamRsp.java new file mode 100644 index 00000000..55e53019 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpAvatarTeamRsp.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.TeamInfo; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetUpAvatarTeamRspOuterClass.SetUpAvatarTeamRsp; + +public class PacketSetUpAvatarTeamRsp extends GenshinPacket { + + public PacketSetUpAvatarTeamRsp(GenshinPlayer player, int teamId, TeamInfo teamInfo) { + super(PacketOpcodes.SetUpAvatarTeamRsp); + + SetUpAvatarTeamRsp.Builder proto = SetUpAvatarTeamRsp.newBuilder() + .setTeamId(teamId) + .setCurAvatarGuid(player.getTeamManager().getCurrentCharacterGuid()); + + for (int avatarId : teamInfo.getAvatars()) { + proto.addAvatarTeamGuidList(player.getAvatars().getAvatarById(avatarId).getGuid()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemChangeNotify.java new file mode 100644 index 00000000..70f86b78 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemChangeNotify.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collection; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.StoreItemChangeNotifyOuterClass.StoreItemChangeNotify; +import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; + +public class PacketStoreItemChangeNotify extends GenshinPacket { + + private PacketStoreItemChangeNotify() { + super(PacketOpcodes.StoreItemChangeNotify); + } + + public PacketStoreItemChangeNotify(GenshinItem item) { + this(); + + StoreItemChangeNotify.Builder proto = StoreItemChangeNotify.newBuilder() + .setStoreType(StoreType.StorePack) + .addItemList(item.toProto()); + + this.setData(proto); + } + + public PacketStoreItemChangeNotify(Collection items) { + this(); + + StoreItemChangeNotify.Builder proto = StoreItemChangeNotify.newBuilder() + .setStoreType(StoreType.StorePack); + + items.stream().forEach(item -> proto.addItemList(item.toProto())); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemDelNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemDelNotify.java new file mode 100644 index 00000000..0bd58572 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreItemDelNotify.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collection; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.StoreItemDelNotifyOuterClass.StoreItemDelNotify; +import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; + +public class PacketStoreItemDelNotify extends GenshinPacket { + + private PacketStoreItemDelNotify() { + super(PacketOpcodes.StoreItemDelNotify); + } + + public PacketStoreItemDelNotify(GenshinItem item) { + this(); + + StoreItemDelNotify.Builder proto = StoreItemDelNotify.newBuilder() + .setStoreType(StoreType.StorePack) + .addGuidList(item.getGuid()); + + this.setData(proto); + } + + public PacketStoreItemDelNotify(Collection items) { + this(); + + StoreItemDelNotify.Builder proto = StoreItemDelNotify.newBuilder() + .setStoreType(StoreType.StorePack); + + items.stream().forEach(item -> proto.addGuidList(item.getGuid())); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java new file mode 100644 index 00000000..ae4418cb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; +import emu.grasscutter.net.proto.StoreWeightLimitNotifyOuterClass.StoreWeightLimitNotify; + +public class PacketStoreWeightLimitNotify extends GenshinPacket { + + public PacketStoreWeightLimitNotify() { + super(PacketOpcodes.StoreWeightLimitNotify); + + StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder() + .setStoreType(StoreType.StorePack) + .setWeightLimit(GenshinConstants.LIMIT_ALL) + .setWeaponCountLimit(GenshinConstants.LIMIT_WEAPON) + .setReliquaryCountLimit(GenshinConstants.LIMIT_RELIC) + .setMaterialCountLimit(GenshinConstants.LIMIT_MATERIAL) + .setFurnitureCountLimit(GenshinConstants.LIMIT_FURNITURE) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSyncScenePlayTeamEntityNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncScenePlayTeamEntityNotify.java new file mode 100644 index 00000000..2983adbc --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncScenePlayTeamEntityNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SyncScenePlayTeamEntityNotifyOuterClass.SyncScenePlayTeamEntityNotify; + +public class PacketSyncScenePlayTeamEntityNotify extends GenshinPacket { + + public PacketSyncScenePlayTeamEntityNotify(GenshinPlayer player) { + super(PacketOpcodes.SyncScenePlayTeamEntityNotify); + + SyncScenePlayTeamEntityNotify proto = SyncScenePlayTeamEntityNotify.newBuilder() + .setSceneId(player.getSceneId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java new file mode 100644 index 00000000..2c0bae90 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSyncTeamEntityNotify.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.SyncTeamEntityNotifyOuterClass.SyncTeamEntityNotify; +import emu.grasscutter.net.proto.TeamEntityInfoOuterClass.TeamEntityInfo; + +public class PacketSyncTeamEntityNotify extends GenshinPacket { + + public PacketSyncTeamEntityNotify(GenshinPlayer player) { + super(PacketOpcodes.SyncTeamEntityNotify); + + SyncTeamEntityNotify.Builder proto = SyncTeamEntityNotify.newBuilder() + .setSceneId(player.getSceneId()); + + if (player.getWorld().isMultiplayer()) { + for (GenshinPlayer p : player.getWorld().getPlayers()) { + // Skip if same player + if (player == p) { + continue; + } + + // Set info + TeamEntityInfo info = TeamEntityInfo.newBuilder() + .setTeamEntityId(p.getTeamManager().getEntityId()) + .setAuthorityPeerId(p.getPeerId()) + .setTeamAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .build(); + + proto.addTeamEntityInfoList(info); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeoffEquipRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeoffEquipRsp.java new file mode 100644 index 00000000..7250491e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeoffEquipRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TakeoffEquipRspOuterClass.TakeoffEquipRsp; + +public class PacketTakeoffEquipRsp extends GenshinPacket { + + public PacketTakeoffEquipRsp(long avatarGuid, int slot) { + super(PacketOpcodes.TakeoffEquipRsp); + + TakeoffEquipRsp proto = TakeoffEquipRsp.newBuilder() + .setAvatarGuid(avatarGuid) + .setSlot(slot) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java new file mode 100644 index 00000000..cb0d75ab --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; +import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; +import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; + +public class PacketTowerAllDataRsp extends GenshinPacket { + + public PacketTowerAllDataRsp() { + super(PacketOpcodes.TowerAllDataRsp); + + TowerAllDataRsp proto = TowerAllDataRsp.newBuilder() + .setTowerScheduleId(29) + .addTowerFloorRecordList(TowerFloorRecord.newBuilder().setFloorId(1001)) + .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) + .setNextScheduleChangeTime(Integer.MAX_VALUE) + .putFloorOpenTimeMap(1024, 1630486800) + .putFloorOpenTimeMap(1025, 1630486800) + .putFloorOpenTimeMap(1026, 1630486800) + .putFloorOpenTimeMap(1027, 1630486800) + .setScheduleStartTime(1630486800) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockAvatarTalentRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockAvatarTalentRsp.java new file mode 100644 index 00000000..7c87f14e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockAvatarTalentRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UnlockAvatarTalentRspOuterClass.UnlockAvatarTalentRsp; + +public class PacketUnlockAvatarTalentRsp extends GenshinPacket { + + public PacketUnlockAvatarTalentRsp(GenshinAvatar avatar, int talentId) { + super(PacketOpcodes.UnlockAvatarTalentRsp); + + UnlockAvatarTalentRsp proto = UnlockAvatarTalentRsp.newBuilder() + .setAvatarGuid(avatar.getGuid()) + .setTalentId(talentId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockNameCardNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockNameCardNotify.java new file mode 100644 index 00000000..9bbd7d5c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockNameCardNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UnlockNameCardNotifyOuterClass.UnlockNameCardNotify; + +public class PacketUnlockNameCardNotify extends GenshinPacket { + + public PacketUnlockNameCardNotify(int nameCard) { + super(PacketOpcodes.UnlockNameCardNotify); + + UnlockNameCardNotify proto = UnlockNameCardNotify.newBuilder() + .setNameCardId(nameCard) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUseItemRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUseItemRsp.java new file mode 100644 index 00000000..0069ee11 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUseItemRsp.java @@ -0,0 +1,29 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UseItemRspOuterClass.UseItemRsp; + +public class PacketUseItemRsp extends GenshinPacket { + + public PacketUseItemRsp(long targetGuid, GenshinItem useItem) { + super(PacketOpcodes.UseItemRsp); + + UseItemRsp proto = UseItemRsp.newBuilder() + .setTargetGuid(targetGuid) + .setItemId(useItem.getItemId()) + .setGuid(useItem.getGuid()) + .build(); + + this.setData(proto); + } + + public PacketUseItemRsp() { + super(PacketOpcodes.UseItemRsp); + + UseItemRsp proto = UseItemRsp.newBuilder().setRetcode(1).build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponAwakenRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponAwakenRsp.java new file mode 100644 index 00000000..6ffba63f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponAwakenRsp.java @@ -0,0 +1,29 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WeaponAwakenRspOuterClass.WeaponAwakenRsp; + +public class PacketWeaponAwakenRsp extends GenshinPacket { + + public PacketWeaponAwakenRsp(GenshinAvatar avatar, GenshinItem item, GenshinItem feedWeapon, int oldRefineLevel) { + super(PacketOpcodes.WeaponAwakenRsp); + + WeaponAwakenRsp.Builder proto = WeaponAwakenRsp.newBuilder() + .setTargetWeaponGuid(item.getGuid()) + .setTargetWeaponAwakenLevel(item.getRefinement()); + + for (int affixId : item.getAffixes()) { + proto.putOldAffixLevelMap(affixId, oldRefineLevel); + proto.putCurAffixLevelMap(affixId, item.getRefinement()); + } + + if (avatar != null) { + proto.setAvatarGuid(avatar.getGuid()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponPromoteRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponPromoteRsp.java new file mode 100644 index 00000000..f647bd1f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponPromoteRsp.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WeaponPromoteRspOuterClass.WeaponPromoteRsp; + +public class PacketWeaponPromoteRsp extends GenshinPacket { + + public PacketWeaponPromoteRsp(GenshinItem item, int oldPromoteLevel) { + super(PacketOpcodes.WeaponPromoteRsp); + + WeaponPromoteRsp proto = WeaponPromoteRsp.newBuilder() + .setTargetWeaponGuid(item.getGuid()) + .setCurPromoteLevel(item.getPromoteLevel()) + .setOldPromoteLevel(oldPromoteLevel) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponUpgradeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponUpgradeRsp.java new file mode 100644 index 00000000..b00accb6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWeaponUpgradeRsp.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import java.util.List; + +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; +import emu.grasscutter.net.proto.WeaponUpgradeRspOuterClass.WeaponUpgradeRsp; + +public class PacketWeaponUpgradeRsp extends GenshinPacket { + + public PacketWeaponUpgradeRsp(GenshinItem item, int oldLevel, List leftoverOres) { + super(PacketOpcodes.WeaponUpgradeRsp); + + WeaponUpgradeRsp proto = WeaponUpgradeRsp.newBuilder() + .setTargetWeaponGuid(item.getGuid()) + .setCurLevel(item.getLevel()) + .setOldLevel(oldLevel) + .addAllItemParamList(leftoverOres) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWearEquipRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWearEquipRsp.java new file mode 100644 index 00000000..26e48566 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWearEquipRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WearEquipRspOuterClass.WearEquipRsp; + +public class PacketWearEquipRsp extends GenshinPacket { + + public PacketWearEquipRsp(long avatarGuid, long equipGuid) { + super(PacketOpcodes.WearEquipRsp); + + WearEquipRsp proto = WearEquipRsp.newBuilder() + .setAvatarGuid(avatarGuid) + .setEquipGuid(equipGuid) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldDataNotify.java new file mode 100644 index 00000000..9a5aeb88 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldDataNotify.java @@ -0,0 +1,24 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; +import emu.grasscutter.net.proto.WorldDataNotifyOuterClass.WorldDataNotify; + +public class PacketWorldDataNotify extends GenshinPacket { + + public PacketWorldDataNotify(World world) { + super(PacketOpcodes.WorldDataNotify); + + int worldLevel = world.getWorldLevel(); + int isMp = world.isMultiplayer() ? 1 : 0; + + WorldDataNotify proto = WorldDataNotify.newBuilder() + .putWorldPropMap(1, PropValue.newBuilder().setType(1).setIval(worldLevel).setVal(worldLevel).build()) + .putWorldPropMap(2, PropValue.newBuilder().setType(2).setIval(isMp).setVal(isMp).build()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerDieNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerDieNotify.java new file mode 100644 index 00000000..f67ff0d5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerDieNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.WorldPlayerDieNotifyOuterClass.WorldPlayerDieNotify; + +public class PacketWorldPlayerDieNotify extends GenshinPacket { + + public PacketWorldPlayerDieNotify(PlayerDieType playerDieType, int killerId) { + super(PacketOpcodes.WorldPlayerDieNotify); + + WorldPlayerDieNotify proto = WorldPlayerDieNotify.newBuilder() + .setDieType(playerDieType) + .setMonsterId(killerId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerInfoNotify.java new file mode 100644 index 00000000..c14c7e43 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerInfoNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WorldPlayerInfoNotifyOuterClass.WorldPlayerInfoNotify; + +public class PacketWorldPlayerInfoNotify extends GenshinPacket { + + public PacketWorldPlayerInfoNotify(World world) { + super(PacketOpcodes.WorldPlayerInfoNotify); + + WorldPlayerInfoNotify.Builder proto = WorldPlayerInfoNotify.newBuilder(); + + for (int i = 0; i < world.getPlayers().size(); i++) { + GenshinPlayer p = world.getPlayers().get(i); + + proto.addPlayerInfoList(p.getOnlinePlayerInfo()); + proto.addPlayerUidList(p.getId()); + } + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java new file mode 100644 index 00000000..cb546f0e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WorldPlayerLocationNotifyOuterClass.WorldPlayerLocationNotify; + +public class PacketWorldPlayerLocationNotify extends GenshinPacket { + + public PacketWorldPlayerLocationNotify(World world) { + super(PacketOpcodes.WorldPlayerLocationNotify); + + WorldPlayerLocationNotify.Builder proto = WorldPlayerLocationNotify.newBuilder(); + + for (GenshinPlayer p : world.getPlayers()) { + proto.addPlayerLocList(p.getPlayerLocationInfo()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerRTTNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerRTTNotify.java new file mode 100644 index 00000000..20b64e41 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerRTTNotify.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; +import emu.grasscutter.net.packet.GenshinPacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerRTTInfoOuterClass.PlayerRTTInfo; +import emu.grasscutter.net.proto.WorldPlayerRTTNotifyOuterClass.WorldPlayerRTTNotify; + +public class PacketWorldPlayerRTTNotify extends GenshinPacket { + + public PacketWorldPlayerRTTNotify(World world) { + super(PacketOpcodes.WorldPlayerRTTNotify); + + WorldPlayerRTTNotify.Builder proto = WorldPlayerRTTNotify.newBuilder(); + + for (GenshinPlayer player : world.getPlayers()) { + proto.addPlayerRttList( + PlayerRTTInfo.newBuilder() + .setUid(player.getId()) + .setRtt(10) // TODO - put player ping here + ); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/tools/Dumpers.java b/src/main/java/emu/grasscutter/tools/Dumpers.java new file mode 100644 index 00000000..e417009a --- /dev/null +++ b/src/main/java/emu/grasscutter/tools/Dumpers.java @@ -0,0 +1,35 @@ +package emu.grasscutter.tools; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import emu.grasscutter.game.props.OpenState; +import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; +import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; +import emu.grasscutter.net.proto.OpenStateUpdateNotifyOuterClass.OpenStateUpdateNotify; +import emu.grasscutter.utils.FileUtils; + +public class Dumpers { + + public static void extractBanner(byte[] data) throws Exception { + GetGachaInfoRsp proto = GetGachaInfoRsp.parseFrom(data); + System.out.println(proto); + } + + public static void extractShop(byte[] data) throws Exception { + GetShopRsp proto = GetShopRsp.parseFrom(data); + System.out.println(proto); + } + + public static void dumpOpenStates(byte[] data) throws Exception { + OpenStateUpdateNotify proto = OpenStateUpdateNotify.parseFrom(data); + + List list = new ArrayList<>(proto.getOpenStateMap().keySet()); + Collections.sort(list); + + for (int key : list) { + System.out.println(OpenState.getTypeByValue(key) + " : " + key); + } + } +} diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java new file mode 100644 index 00000000..fede4086 --- /dev/null +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -0,0 +1,77 @@ +package emu.grasscutter.tools; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.google.gson.reflect.TypeToken; + +import emu.grasscutter.GenshinConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GenshinData; +import emu.grasscutter.data.ResourceLoader; +import emu.grasscutter.data.def.AvatarData; +import emu.grasscutter.data.def.ItemData; +import emu.grasscutter.data.def.MonsterData; + +public class Tools { + + @SuppressWarnings("deprecation") + public static void createGmHandbook() throws Exception { + ResourceLoader.loadResources(); + + Map map; + try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMapEN.json")) { + map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); + } + + List list; + String fileName = "./GM Handbook.txt"; + try (FileWriter fileWriter = new FileWriter(fileName); PrintWriter writer = new PrintWriter(fileWriter)) { + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + LocalDateTime now = LocalDateTime.now(); + + writer.println("// Genshin Impact " + GenshinConstants.VERSION + " GM Handbook"); + writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); + + list = GenshinData.getAvatarDataMap().keySet().stream().collect(Collectors.toList()); + Collections.sort(list); + + writer.println("// Avatars"); + for (Integer id : list) { + AvatarData data = GenshinData.getAvatarDataMap().get(id); + writer.println(data.getId() + " : " + map.get(data.getNameTextMapHash())); + } + + writer.println(); + + list = GenshinData.getItemDataMap().keySet().stream().collect(Collectors.toList()); + Collections.sort(list); + + writer.println("// Items"); + for (Integer id : list) { + ItemData data = GenshinData.getItemDataMap().get(id); + writer.println(data.getId() + " : " + map.get(data.getNameTextMapHash())); + } + + writer.println(); + + writer.println("// Monsters"); + list = GenshinData.getMonsterDataMap().keySet().stream().collect(Collectors.toList()); + Collections.sort(list); + + for (Integer id : list) { + MonsterData data = GenshinData.getMonsterDataMap().get(id); + writer.println(data.getId() + " : " + map.get(data.getNameTextMapHash())); + } + } + + Grasscutter.getLogger().info("GM Handbook generated!"); + } +} diff --git a/src/main/java/emu/grasscutter/utils/Crypto.java b/src/main/java/emu/grasscutter/utils/Crypto.java new file mode 100644 index 00000000..a44cbfb9 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/Crypto.java @@ -0,0 +1,59 @@ +package emu.grasscutter.utils; + +import java.security.SecureRandom; +import java.util.Base64; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; +import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; + +public class Crypto { + private static SecureRandom secureRandom = new SecureRandom(); + public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); + public static byte[] ENCRYPT_SEED_BUFFER = new byte[0]; + + public static byte[] DISPATCH_KEY; + public static byte[] ENCRYPT_KEY; + + public static void loadKeys() { + DISPATCH_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchKey.bin"); + ENCRYPT_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKey.bin"); + ENCRYPT_SEED_BUFFER = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin"); + } + + public static void xor(byte[] packet, byte[] key) { + try { + for (int i = 0; i < packet.length; i++) { + packet[i] ^= key[i % key.length]; + } + } catch (Exception e) { + Grasscutter.getLogger().error("Crypto error.", e); + } + } + + public static void extractSecretKeyBuffer(byte[] data) { + try { + GetPlayerTokenRsp p = GetPlayerTokenRsp.parseFrom(data); + FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin", p.getSecretKeyBuffer().toByteArray()); + Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void extractDispatchSeed(String data) { + try { + QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data)); + FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static byte[] createSessionKey(int length) { + byte[] bytes = new byte[length]; + secureRandom.nextBytes(bytes); + return bytes; + } +} diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java new file mode 100644 index 00000000..5398ee65 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -0,0 +1,48 @@ +package emu.grasscutter.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileUtils { + + public static void write(String dest, byte[] bytes) { + Path path = Paths.get(dest); + + try { + Files.write(path, bytes); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static byte[] read(String dest) { + return read(Paths.get(dest)); + } + + public static byte[] read(Path path) { + try { + return Files.readAllBytes(path); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return new byte[0]; + } + + public static byte[] read(File file) { + return read(file.getPath()); + } + + public static String getFilenameWithoutPath(String fileName) { + if (fileName.indexOf(".") > 0) { + return fileName.substring(0, fileName.lastIndexOf(".")); + } else { + return fileName; + } + } +} diff --git a/src/main/java/emu/grasscutter/utils/Position.java b/src/main/java/emu/grasscutter/utils/Position.java new file mode 100644 index 00000000..6a02dacb --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/Position.java @@ -0,0 +1,156 @@ +package emu.grasscutter.utils; + +import java.io.Serializable; + +import emu.grasscutter.net.proto.VectorOuterClass.Vector; + +public class Position implements Serializable { + private static final long serialVersionUID = -2001232313615923575L; + + private float x; + private float y; + private float z; + + public Position() { + + } + + public Position(float x, float y) { + set(x, y); + } + + public Position(float x, float y, float z) { + set(x, y, z); + } + + public Position(String p) { + String[] split = p.split(","); + if (split.length >= 2) { + this.x = Float.parseFloat(split[0]); + this.y = Float.parseFloat(split[1]); + } + if (split.length >= 3) { + this.z = Float.parseFloat(split[2]); + } + } + + public Position(Vector vector) { + this.set(vector); + } + + public Position(Position pos) { + this.set(pos); + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public Position set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + // Deep copy + public Position set(Position pos) { + return this.set(pos.getX(), pos.getY(), pos.getZ()); + } + + public Position set(Vector pos) { + return this.set(pos.getX(), pos.getY(), pos.getZ()); + } + + public Position set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Position add(Position add) { + this.x += add.getX(); + this.y += add.getY(); + this.z += add.getZ(); + return this; + } + + public Position addX(float d) { + this.x += d; + return this; + } + + public Position addY(float d) { + this.y += d; + return this; + } + + public Position addZ(float d) { + this.z += d; + return this; + } + + public Position subtract(Position sub) { + this.x -= sub.getX(); + this.y -= sub.getY(); + this.z -= sub.getZ(); + return this; + } + + /** In radians + * */ + public Position translate(float dist, float angle) { + this.x += dist * Math.sin(angle); + this.y += dist * Math.cos(angle); + return this; + } + + public boolean equal2d(Position other) { + return getX() == other.getX() && getY() == other.getY(); + } + + public Position translateWithDegrees(float dist, float angle) { + angle = (float) Math.toRadians(angle); + this.x += dist * Math.sin(angle); + this.y += -dist * Math.cos(angle); + return this; + } + + @Override + public Position clone() { + return new Position(x, y, z); + } + + @Override + public String toString() { + return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")"; + } + + public Vector toProto() { + return Vector.newBuilder() + .setX(this.getX()) + .setY(this.getY()) + .setZ(this.getZ()) + .build(); + } +} diff --git a/src/main/java/emu/grasscutter/utils/ProtoHelper.java b/src/main/java/emu/grasscutter/utils/ProtoHelper.java new file mode 100644 index 00000000..70aba3e3 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/ProtoHelper.java @@ -0,0 +1,10 @@ +package emu.grasscutter.utils; + +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; + +public class ProtoHelper { + public static PropValue newPropValue(PlayerProperty key, int value) { + return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build(); + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java new file mode 100644 index 00000000..cbfcb7ec --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -0,0 +1,79 @@ +package emu.grasscutter.utils; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Random; + +import emu.grasscutter.Grasscutter; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; + +public class Utils { + public static final Random random = new Random(); + + public static int randomRange(int min, int max) { + return random.nextInt(max - min + 1) + min; + } + + public static float randomFloatRange(float min, float max) { + return random.nextFloat() * (max - min) + min; + } + + public static int getCurrentSeconds() { + return (int) (System.currentTimeMillis() / 1000.0); + } + + public static String lowerCaseFirstChar(String s) { + StringBuilder sb = new StringBuilder(s); + sb.setCharAt(0, Character.toLowerCase(sb.charAt(0))); + return sb.toString(); + } + + public static String toString(InputStream inputStream) throws IOException { + BufferedInputStream bis = new BufferedInputStream(inputStream); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (int result = bis.read(); result != -1; result = bis.read()) { + buf.write((byte) result); + } + return buf.toString(); + } + + public static void logByteArray(byte[] array) { + ByteBuf b = Unpooled.wrappedBuffer(array); + Grasscutter.getLogger().info("\n" + ByteBufUtil.prettyHexDump(b)); + b.release(); + } + + private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + public static String bytesToHex(ByteBuf buf) { + return bytesToHex(byteBufToArray(buf)); + } + + public static byte[] byteBufToArray(ByteBuf buf) { + byte[] bytes = new byte[buf.capacity()]; + buf.getBytes(0, bytes); + return bytes; + } + + public static int abilityHash(String str) { + int v7 = 0; + int v8 = 0; + while (v8 < str.length()) { + v7 = str.charAt(v8++) + 131 * v7; + } + return v7; + } +} diff --git a/src/main/java/emu/grasscutter/utils/WeightedList.java b/src/main/java/emu/grasscutter/utils/WeightedList.java new file mode 100644 index 00000000..6c3e3222 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/WeightedList.java @@ -0,0 +1,30 @@ +package emu.grasscutter.utils; + +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.concurrent.ThreadLocalRandom; + +public class WeightedList { + private final NavigableMap map = new TreeMap(); + private double total = 0; + + public WeightedList() { + + } + + public WeightedList add(double weight, E result) { + if (weight <= 0) return this; + total += weight; + map.put(total, result); + return this; + } + + public E next() { + double value = ThreadLocalRandom.current().nextDouble() * total; + return map.higherEntry(value).getValue(); + } + + public int size() { + return map.size(); + } +} \ No newline at end of file diff --git a/src/main/java/logback.xml b/src/main/java/logback.xml new file mode 100644 index 00000000..66d63019 --- /dev/null +++ b/src/main/java/logback.xml @@ -0,0 +1,11 @@ + + + + [%d{HH:mm:ss}] [%level] %msg%n + + + + + + + \ No newline at end of file