mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-07-04 05:53:42 +00:00
Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
205b79dc02 | |||
0e033e3f77 | |||
583a41ab2c | |||
cf6fb275be | |||
269f7b4fbf | |||
9b4ce34f4a | |||
f86259a430 | |||
837e30e04b | |||
f5703e5964 | |||
bc8e7c21ce | |||
b7a9d28f02 | |||
770cd62370 | |||
6745d1126e | |||
0803618bf5 | |||
cfc8a4866f | |||
fd75ba7b9b | |||
d32a75e980 | |||
9a198bd231 | |||
453dc9717d | |||
582d7af9c4 | |||
cab3bfb5a7 | |||
cf574e99cb | |||
3094facb88 | |||
6e309b6fee | |||
b5e35f5409 | |||
a3fd10c3be | |||
b6e7d69949 | |||
5faf39d359 | |||
0dd95450b1 | |||
0f0e7aca68 | |||
5ee4812ac5 | |||
ec2bfffdd1 | |||
7f5059cb8f | |||
43db7eba8f | |||
ff6a51db30 | |||
047feaf4aa | |||
88315ec712 | |||
fdad4218e7 | |||
5f5e6c38b1 | |||
92bd09eeed | |||
30f7580184 | |||
65eaaa96f2 | |||
70a961e167 | |||
dd78addc29 | |||
43467ebc85 | |||
1f15d6219b | |||
fb0c2dbc84 | |||
b8f7aea168 | |||
cf8092e8ba | |||
0e44d18ae9 | |||
5bd8f532c1 | |||
ad62b6b11d | |||
c9a43a5e98 | |||
5458d36102 | |||
c4dbb6851b | |||
2643c6b3b7 | |||
84e1371499 | |||
f955bb1e16 | |||
2b64814534 | |||
ea5ee075a7 | |||
6108a3bb37 | |||
98a83b649e | |||
4f62e484ad | |||
5fd31aece8 | |||
8de281d4da | |||
fbe2b138ee | |||
47c96fd964 | |||
04370f1a21 | |||
7845c54570 | |||
818b638bed | |||
a9402f487f | |||
cdcdf924bd | |||
fc42f665a7 | |||
83602f78ae | |||
8db1f597ce |
42
README.md
42
README.md
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
- Get Java 17: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
- Get Java 17: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||||
- Get [MongoDB Community Server](https://www.mongodb.com/try/download/community)
|
- Get [MongoDB Community Server](https://www.mongodb.com/try/download/community)
|
||||||
- Get game version REL3.7 (3.7 client can be found here if you don't have it): https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/3.7.0.md
|
- Get game version REL4.0.x (4.0.x client can be found here if you don't have it): https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/4.0.0.md
|
||||||
|
|
||||||
- Download the [latest Cultivation version](https://github.com/Grasscutters/Cultivation/releases/latest). Use the `.msi` installer.
|
- Download the [latest Cultivation version](https://github.com/Grasscutters/Cultivation/releases/latest). Use the `.msi` installer.
|
||||||
- After opening Culivation (as admin), press the download button in the upper right corner.
|
- After opening Culivation (as admin), press the download button in the upper right corner.
|
||||||
@ -46,25 +46,49 @@ Grasscutter uses Gradle to handle dependencies & building.
|
|||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
|
|
||||||
- [Java SE Development Kits - 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or higher
|
- [Java Development Kit 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or higher
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
- [NodeJS](https://nodejs.org/en/download) (Optional, for building the handbook)
|
||||||
|
|
||||||
##### Windows
|
##### Clone
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
||||||
cd Grasscutter
|
cd Grasscutter
|
||||||
.\gradlew.bat # Setting up environments
|
|
||||||
.\gradlew jar # Compile
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Linux (GNU)
|
##### Compile
|
||||||
|
|
||||||
|
**Note**: Handbook generation may fail on some systems. To disable the handbook generation, append `-PskipHandbook=1` to the `gradlew jar` command.
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
.\gradlew.bat # Setting up environments
|
||||||
|
.\gradlew jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Linux (GNU):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
|
||||||
cd Grasscutter
|
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew jar # Compile
|
./gradlew jar
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Compiling the Handbook (Manually)
|
||||||
|
|
||||||
|
With Gradle:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./gradlew generateHandbook
|
||||||
|
```
|
||||||
|
|
||||||
|
With NPM:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd src/handbook
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find the output jar in the root of the project folder.
|
You can find the output jar in the root of the project folder.
|
||||||
|
@ -58,7 +58,7 @@ sourceCompatibility = JavaVersion.VERSION_17
|
|||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
||||||
group = 'io.grasscutter'
|
group = 'io.grasscutter'
|
||||||
version = '1.7.1'
|
version = '1.7.3'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
withJavadocJar()
|
withJavadocJar()
|
||||||
@ -386,6 +386,12 @@ tasks.register('generateHandbook') {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install dependencies before building.
|
||||||
|
exec {
|
||||||
|
workingDir 'src/handbook'
|
||||||
|
commandLine npm, 'install'
|
||||||
|
}
|
||||||
|
|
||||||
// Build the handbook.
|
// Build the handbook.
|
||||||
exec {
|
exec {
|
||||||
workingDir 'src/handbook'
|
workingDir 'src/handbook'
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**תשומת לב בבקשה:** אנחנו מקבלים עזרה בפיתוח התוכנה. לפני שאתם תורמים לפרויקט בבקשה תקראו את [תנאי השימוש](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**תשומת לב בבקשה:** אנחנו מקבלים עזרה בפיתוח התוכנה. לפני שאתם תורמים לפרויקט בבקשה תקראו את [תנאי השימוש](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Aantekening:** We verwelkomen altijd bijdragers aan het project. Lees onze [Gedragscode](https://github.com/Grasscutters/Grasscutter/blob/development/README_NL.md#bijdragen-aan-het-project) zorgvuldig door voordat u uw bijdrage toevoegt.
|
**Aantekening:** We verwelkomen altijd bijdragers aan het project. Lees onze [Gedragscode](https://github.com/Grasscutters/Grasscutter/blob/development/README_NL.md#bijdragen-aan-het-project) zorgvuldig door voordat u uw bijdrage toevoegt.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Atención:** Siempre damos la bienvenida a contribuidores del proyecto. Antes de añadir tu contribución, por favor lee cuidadosamente nuestro [Código de conducta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Atención:** Siempre damos la bienvenida a contribuidores del proyecto. Antes de añadir tu contribución, por favor lee cuidadosamente nuestro [Código de conducta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Atensyon:** Ang mga kontributor ay laging welcome sa proyektong ito. Bago mag-bigay ng kontribusyon, basahin muna ng mabuti ang [Code of Conduct](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Atensyon:** Ang mga kontributor ay laging welcome sa proyektong ito. Bago mag-bigay ng kontribusyon, basahin muna ng mabuti ang [Code of Conduct](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Attention:** De nouveaux contributeurs sont toujours les bienvenus. Avant d'ajouter votre contribution, veuillez lire le [code de conduite](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Attention:** De nouveaux contributeurs sont toujours les bienvenus. Avant d'ajouter votre contribution, veuillez lire le [code de conduite](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
@ -71,4 +71,4 @@ Vous pouvez trouver le jar de sortie dans la racine du dossier du projet.
|
|||||||
|
|
||||||
### Dépanage
|
### Dépanage
|
||||||
|
|
||||||
Pour une liste des problèmes communs et leur solution et pour demander de l'aide, veuillez rejoindre [notre serveur Discord](https://discord.gg/T5vZU6UyeG) (en anglais) et dirigez vous vers le salon de support.
|
Pour une liste des problèmes communs et leur solution et pour demander de l'aide, veuillez rejoindre [notre serveur Discord](https://discord.gg/T5vZU6UyeG) (en anglais) et dirigez vous vers le salon de support.
|
||||||
|
78
docs/README_hn-IN.md
Normal file
78
docs/README_hn-IN.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|

|
||||||
|
<div align="center"><img alt="Documentation" src="https://img.shields.io/badge/Wiki-Grasscutter-blue?style=for-the-badge&link=https://github.com/Grasscutters/Grasscutter/wiki&link=https://github.com/Grasscutters/Grasscutter/wiki"> <img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Grasscutters/Grasscutter?logo=java&style=for-the-badge"> <img alt="GitHub" src="https://img.shields.io/github/license/Grasscutters/Grasscutter?style=for-the-badge"> <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/Grasscutters/Grasscutter?style=for-the-badge"> <img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/Grasscutters/Grasscutter/build.yml?branch=development&logo=github&style=for-the-badge"></div>
|
||||||
|
|
||||||
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
|
[EN](README.md) | [简中](docs/README_zh-CN.md) | [繁中](docs/README_zh-TW.md) | [FR](docs/README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
|
**ध्यान:** हम हमेशा परियोजना में योगदानकर्ताओं का स्वागत करते हैं।. अपना योगदान जोड़ने से पहले कृपया हमारा ध्यानपूर्वक पढ़ें [आचार संहिता](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## वर्तमान सुविधाएँ
|
||||||
|
|
||||||
|
* लॉग इन करना
|
||||||
|
* युद्ध
|
||||||
|
* मित्रों की सूची
|
||||||
|
* टेलीपोर्टेशन
|
||||||
|
* गाचा प्रणाली
|
||||||
|
* सह-ऑप * आंशिक रूप से * काम करता है
|
||||||
|
* कंसोल के माध्यम से राक्षसों को जन्म देना
|
||||||
|
* इन्वेंट्री सुविधाएँ (आइटम / वर्ण प्राप्त करना, आइटम / वर्णों को अपग्रेड करना, आदि)
|
||||||
|
|
||||||
|
## त्वरित सेटअप गाइड
|
||||||
|
|
||||||
|
**टिप्पणी**: समर्थन के लिए कृपया हमसे जुड़ें [Discord](https://discord.gg/T5vZU6UyeG).
|
||||||
|
|
||||||
|
### त्वरित प्रारंभ (स्वचालित)
|
||||||
|
|
||||||
|
- Get Java 17: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||||
|
|
||||||
|
**ध्यान दें:** बस **सर्वर शुरू करने** के लिए, आपको बस **jre** की आवश्यकता है।
|
||||||
|
- Get [MongoDB Community Server](https://www.mongodb.com/try/download/community)
|
||||||
|
|
||||||
|
* प्रॉक्सी: मिटमडंप (अनुशंसित), मिटमप्रॉक्सी, फिडलर क्लासिक, आदि।
|
||||||
|
- गेम संस्करण REL3.7 प्राप्त करें (यदि आपके पास 3.7 क्लाइंट नहीं है तो उसे यहां पाया जा सकता है):: https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/3.7.0.md
|
||||||
|
|
||||||
|
- डाउनलोड करें [latest Cultivation version](https://github.com/Grasscutters/Cultivation/releases/latest). उपयोग `.msi` इंस्टालरr.
|
||||||
|
- कलिवेशन (एडमिन के रूप में) खोलने के बाद, ऊपरी दाएं कोने में डाउनलोड बटन दबाएं।
|
||||||
|
- `डाउनलोड ऑल-इन-वन` पर क्लिक करें
|
||||||
|
- ऊपरी दाएं कोने में गियर पर क्लिक करें
|
||||||
|
- गेम इंस्टॉल पथ को उस स्थान पर सेट करें जहां आपका गेम स्थित है.
|
||||||
|
- कस्टम जावा पथ को इस पर सेट करें `C:\Program Files\Java\jdk-17\bin\java.exe`
|
||||||
|
- अन्य सभी सेटिंग्स को डिफ़ॉल्ट पर छोड़ दें
|
||||||
|
|
||||||
|
- लॉन्च करने के लिए आगे छोटे बटन पर क्लिक करें.
|
||||||
|
- लॉन्च बटन पर क्लिक करें.
|
||||||
|
- आप जो भी उपयोगकर्ता नाम चाहते हैं उसके साथ लॉग इन करें। पासवर्ड कोई मायने नहीं रखता.
|
||||||
|
|
||||||
|
### इमारत
|
||||||
|
|
||||||
|
ग्रासकटर निर्भरता और निर्माण को संभालने के लिए ग्रैडल का उपयोग करता है।
|
||||||
|
|
||||||
|
**आवश्यकताएं:**
|
||||||
|
|
||||||
|
- [Java SE Development Kits - 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or higher
|
||||||
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
|
##### विंडोज
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
||||||
|
cd Grasscutter
|
||||||
|
.\gradlew.bat # Setting up environments
|
||||||
|
.\gradlew jar # Compile
|
||||||
|
```
|
||||||
|
|
||||||
|
##### लिनक्स (जीएनयू)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
||||||
|
cd Grasscutter
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew jar # Compile
|
||||||
|
```
|
||||||
|
|
||||||
|
आप आउटपुट जार को प्रोजेक्ट फ़ोल्डर के रूट में पा सकते हैं।.
|
||||||
|
|
||||||
|
### समस्या निवारण
|
||||||
|
|
||||||
|
सामान्य मुद्दों और समाधानों की सूची और सहायता मांगने के लिए कृपया शामिल हों [our Discord server](https://discord.gg/T5vZU6UyeG) और सपोर्ट चैनल पर जाएं.
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Perhatian:** Kami selalu menyambut kontributor untuk proyek ini. Sebelum menambahkan kontribusi Anda, harap baca [Kode Etik](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) kami.
|
**Perhatian:** Kami selalu menyambut kontributor untuk proyek ini. Sebelum menambahkan kontribusi Anda, harap baca [Kode Etik](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) kami.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Attenzione:** Diamo sempre il benvenuto ai contributori del progetto. Prima di contribuire, leggi attentamente il nostro [Codice di condotta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Attenzione:** Diamo sempre il benvenuto ai contributori del progetto. Prima di contribuire, leggi attentamente il nostro [Codice di condotta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
|
|
||||||
***:** 私たちはプロジェクトへの貢献者をいつでも歓迎します。貢献を追加する前に、我々の [行動規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)をよくお読みください。
|
***:** 私たちはプロジェクトへの貢献者をいつでも歓迎します。貢献を追加する前に、我々の [行動規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)をよくお読みください。
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**주의 :** 우리는 항상 프로젝트에 기여하는 사람들을 환영합니다. 기여를 하기 전, [행동 지침](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)을 주의 깊게 읽어주세요.
|
**주의 :** 우리는 항상 프로젝트에 기여하는 사람들을 환영합니다. 기여를 하기 전, [행동 지침](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)을 주의 깊게 읽어주세요.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Uwaga:** Zawsze jesteśmy otwarci na wasz wkład w projekt. Przed zaproponowaniem zmian przeczytaj [zasady postępowania (ENG)](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Uwaga:** Zawsze jesteśmy otwarci na wasz wkład w projekt. Przed zaproponowaniem zmian przeczytaj [zasady postępowania (ENG)](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Внимание:** Мы всегда рады новому вкладу в проект. Однако, перед тем, как сделать свой вклад, пожалуйста, прочтите наш [кодекс делового поведения](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**Внимание:** Мы всегда рады новому вкладу в проект. Однако, перед тем, как сделать свой вклад, пожалуйста, прочтите наш [кодекс делового поведения](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Chú ý:** Chúng tôi luôn chào đón những người đóng góp cho dự án. Trước khi đóng góp, xin vui lòng đọc kỹ ["các quy tắc" (Code of Conduct)](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) của chúng tôi .
|
**Chú ý:** Chúng tôi luôn chào đón những người đóng góp cho dự án. Trước khi đóng góp, xin vui lòng đọc kỹ ["các quy tắc" (Code of Conduct)](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) của chúng tôi .
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**注意:** 我们始终欢迎项目的贡献者。但在做贡献之前,请仔细阅读我们的[代码规范](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。
|
**注意:** 我们始终欢迎项目的贡献者。但在做贡献之前,请仔细阅读我们的[代码规范](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。
|
||||||
|
|
||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
- 获取Java 17:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
- 获取Java 17:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||||
- 获取[MongoDB社区版](https://www.mongodb.com/try/download/community)
|
- 获取[MongoDB社区版](https://www.mongodb.com/try/download/community)
|
||||||
- 获取游戏3.7正式版 (如果你没有3.7的客户端,可以在这里找到):https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/3.7.0.md)
|
- 获取游戏4.0正式版 (如果你没有4.0的客户端,可以在这里找到):https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/4.0.0.md)
|
||||||
|
|
||||||
- 下载[最新的Cultivation版本](https://github.com/Grasscutters/Cultivation/releases/latest)(使用以“.msi”为后缀的安装包)。
|
- 下载[最新的Cultivation版本](https://github.com/Grasscutters/Cultivation/releases/latest)(使用以“.msi”为后缀的安装包)。
|
||||||
- 以管理员身份打开Culivation,按右上角的下载按钮。
|
- 以管理员身份打开Culivation,按右上角的下载按钮。
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**請注意:** 歡迎成為本專案的貢獻者。在提交 PR 之前, 請仔細閱讀[程式碼規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。
|
**請注意:** 歡迎成為本專案的貢獻者。在提交 PR 之前, 請仔細閱讀[程式碼規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。
|
||||||
|
|
||||||
|
188
docs/events/windtrace/README.md
Normal file
188
docs/events/windtrace/README.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Hide and Seek!
|
||||||
|
Documentation on how the **Hide and Seek** game works.\
|
||||||
|
Externally dubbed: `Windtrace`.
|
||||||
|
|
||||||
|
# Map IDs
|
||||||
|
TODO: Document the map IDs of Windtrace.
|
||||||
|
|
||||||
|
TODO: Investigate `ServerGlobalValueChangeNotify`
|
||||||
|
|
||||||
|
# Asking Players to Play in a Co-Op Game
|
||||||
|
1. The client will send `DraftOwnerStartInviteReq`
|
||||||
|
2. The server will send `DraftOwnerInviteNotify` to all clients.
|
||||||
|
3. The server will send `DraftOwnerStartInviteRsp`
|
||||||
|
|
||||||
|
# Matching in a Co-Op Game
|
||||||
|
1. World owner talks to Gygax and begins a Windtrace game.
|
||||||
|
2. The packet `DraftOwnerInviteNotify` is sent to clients.
|
||||||
|
3. Clients will respond with `DraftGuestReplyInviteReq` (client-side)
|
||||||
|
4. The server will respond with `DraftGuestReplyInviteRsp`
|
||||||
|
5. The server will respond with `DraftInviteResultNotify`
|
||||||
|
|
||||||
|
# Starting Windtrace
|
||||||
|
1. If `DraftInviteResultNotify` is a success, the server will send a series of packets.
|
||||||
|
1. A series of `SceneEntityAppearNotify` packets.
|
||||||
|
2. `NpcTalkStateNotify`
|
||||||
|
3. `PlayerEnterSceneNotify`
|
||||||
|
4. `MultistagePlayInfoNotify`
|
||||||
|
2. The players are then teleported to the Windtrace map in their locations.
|
||||||
|
3. Server will send packets to clients. (this is server boilerplate)
|
||||||
|
4. The server sends another `MultistagePlayInfoNotify` to clients.
|
||||||
|
|
||||||
|
# Changing Avatars - Others
|
||||||
|
1. The server will send a `AvatarEquipChangeNotify` packet to clients.
|
||||||
|
2. The server will send a `SceneTeamUpdateNotify` packet to clients.
|
||||||
|
3. The server will send a `HideAndSeekPlayerSetAvatarNotify` packet to clients.
|
||||||
|
|
||||||
|
# Getting Ready
|
||||||
|
1. The client will send `HideAndSeekSetReadyReq` to the server.
|
||||||
|
2. The server will reply with `HideAndSeekPlayerReadyNotify` to clients.
|
||||||
|
3. The server will send `MultistagePlayInfoNotify` to clients.
|
||||||
|
4. The server will reply with `HideAndSeekSetReadyRsp` to the client.
|
||||||
|
5. If all players are ready, the server will move on to start Windtrace.
|
||||||
|
|
||||||
|
# Starting Windtrace
|
||||||
|
1. When all players are ready, the server will send a series of packets to players.
|
||||||
|
1. `GalleryStartNotify`
|
||||||
|
2. `SceneGalleryInfoNotify`
|
||||||
|
3. `MultistagePlayInfoNotify`
|
||||||
|
4. `MultistagePlayStageEndNotify`
|
||||||
|
5. This will only get sent at the `1.` countdown.
|
||||||
|
|
||||||
|
### Notes:
|
||||||
|
- `GuestReplyInviteRsp` is sent **after** `DraftInviteResultNotify`.
|
||||||
|
|
||||||
|
## `DraftOwnerInviteNotify`
|
||||||
|
- `invite_deadline_time` - This is the time when the invite expires.
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
|
||||||
|
## `DraftOwnerStartInviteReq`
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
|
||||||
|
## `DraftOwnerStartInviteRsp`
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
- `invite_fail_info_list` - A list of players who weren't invited.
|
||||||
|
- `retcode` - The response code.
|
||||||
|
- `wrong_uid` - Always `0`. (undocumented)
|
||||||
|
|
||||||
|
## `DraftGuestReplyInviteReq`
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
- `is_agree` - A boolean value for whether the client accepts the invite.
|
||||||
|
|
||||||
|
## `DraftGuestReplyInviteRsp`
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
- `retcode` - Response code for the request.
|
||||||
|
- `is_agree` - A boolean value for whether the server acknowledges the client's invite acceptation.
|
||||||
|
|
||||||
|
## `DraftInviteResultNotify`
|
||||||
|
- `draft_id` - The value is always `3001` for Windtrace.
|
||||||
|
- `is_all_agree` - A boolean value for whether all clients accepted the invite.
|
||||||
|
|
||||||
|
## `NpcTalkStateNotify`
|
||||||
|
- `is_ban` - This value is always true when entering Windtrace.
|
||||||
|
|
||||||
|
## `PlayerEnterSceneNotify`
|
||||||
|
- `pos` - This is where the player will be teleported to.
|
||||||
|
- This value depends on if the player is a hunter or a runner.
|
||||||
|
- This value is set by the server and must be hardcoded/read from a JSON file.
|
||||||
|
|
||||||
|
## `MultistagePlayStageEndNotify`
|
||||||
|
- `play_index` - Value picked by the server. (use 1)
|
||||||
|
- `group_id` - This value is always `133002121` for Windtrace.
|
||||||
|
|
||||||
|
## `MultistagePlayInfoNotify` - Initial + PostEnterSceneReq
|
||||||
|
- Image Reference: 
|
||||||
|
- `info` - MultistagePlayInfo data.
|
||||||
|
- `group_id` - The value is always `133002121` for Windtrace.
|
||||||
|
- `play_index` - Value picked by the server. (use 1)
|
||||||
|
- `hide_and_seek_info` - Information about Windtrace.
|
||||||
|
- `hider_uid_list` - A list of UIDs (ints) of the hiders.
|
||||||
|
- `hunter_uid` - The UID (int) of the hunter.
|
||||||
|
- `map_id` - The ID of the Windtrace map.
|
||||||
|
- `stage_type` - Windtrace state.
|
||||||
|
- This will be `HIDE_AND_SEEK_STAGE_TYPE_PREPARE`.
|
||||||
|
- `battle_info_map` - Contains a dictionary of UID -> `HideAndSeekPlayerBattleInfo` objects.
|
||||||
|
- `skill_list` - Array of 3 values of skill IDs chosen by the player.
|
||||||
|
- `avatar_id` - The ID of the avatar the player wants to use.
|
||||||
|
- `is_ready` - The player's in-game ready state.
|
||||||
|
- `costume_id` - The costume the player's avatar is wearing.
|
||||||
|
|
||||||
|
## `MultistagePlayInfoNotify` - Picking Avatars
|
||||||
|
- Image Reference: 
|
||||||
|
- **Note:** This packet matches the initial structure and data.
|
||||||
|
- `info.hide_and_seek_info.stage_type` - This will be `HIDE_AND_SEEK_STAGE_TYPE_PICK`.
|
||||||
|
|
||||||
|
## `MultistagePlayInfoNotify` - Starting Windtrace
|
||||||
|
- Image Reference: 
|
||||||
|
- **Note:** This packet matches the initial structure and data.
|
||||||
|
- `info.hide_and_seek_info.stage_type` - This will be `HIDE_AND_SEEK_STAGE_TYPE_HIDE`.
|
||||||
|
|
||||||
|
## `MultistagePlayInfoNotify` - Seeking Time
|
||||||
|
- Image Reference: 
|
||||||
|
- **Note:** This packet matches the initial structure and data.
|
||||||
|
- `info.hide_and_seek_info.stage_type` - This will be `HIDE_AND_SEEK_STAGE_TYPE_SEEK`.
|
||||||
|
|
||||||
|
## `MultistagePlayInfoNotify` - Finish Windtrace
|
||||||
|
- Image Reference: 
|
||||||
|
- **Note:** This packet matches the initial structure and data.
|
||||||
|
- `info.hide_and_seek_info.stage_type` - This will be `HIDE_AND_SEEK_STAGE_TYPE_SETTLE`.
|
||||||
|
|
||||||
|
## `HideAndSeekPlayerSetAvatarNotify`
|
||||||
|
- `avatar_id` - The ID of the new avatar the player wants to use.
|
||||||
|
- `uid` - The UID of the player who changed their avatar.
|
||||||
|
- `costume_id` - The costume the player's avatar is wearing.
|
||||||
|
|
||||||
|
## `HideAndSeekSetReadyRsp`
|
||||||
|
- `retcode` - Response code for the request.
|
||||||
|
|
||||||
|
## `HideAndSeekPlayerReadyNotify`
|
||||||
|
- `uid_list` - A list of UIDs (ints) of the players who are ready.
|
||||||
|
|
||||||
|
## `GalleryStartNotify`
|
||||||
|
- `gallery_id` - TODO: Check if this value is always `7056` for Windtrace.
|
||||||
|
- `start_time` - This value is always `2444` for Windtrace.
|
||||||
|
- This value is `200` when displaying game end statistics.
|
||||||
|
- `owner_uid` - The UID of the player who started the Windtrace game.
|
||||||
|
- `player_count` - The number of players in the Windtrace game.
|
||||||
|
- `end_time` - This value is always the same as `start_time`.
|
||||||
|
|
||||||
|
## `SceneGalleryInfoNotify` - Starting Windtrace
|
||||||
|
- `gallery_info` - SceneGalleryInfo data.
|
||||||
|
- `end_time` - This value is always the same as `start_time`.
|
||||||
|
- `start_time` - This value is always `2444` for Windtrace.
|
||||||
|
- This value is `200` when displaying game end statistics.
|
||||||
|
- `gallery_id` - This value is always the same as `gallery_id` from `GalleryStartNotify`.
|
||||||
|
- `stage` - The current stage of the gallery.
|
||||||
|
- This will be `GALLERY_STAGE_TYPE_START`.
|
||||||
|
- `owner_uid` - The UID of the player who started the Windtrace game.
|
||||||
|
- `hide_and_seek_info` - SceneGalleryHideAndSeekInfo
|
||||||
|
- `visible_uid_list` - List of UIDs (ints) of the players who were left alive.
|
||||||
|
- `caught_uid_list` - List of UIDs (ints) of the players who have been caught.
|
||||||
|
- `player_count` - The amount of players in the Windtrace game.
|
||||||
|
- `pre_start_end_time` - This value is always `0` for Windtrace.
|
||||||
|
|
||||||
|
## `HideAndSeekSettleNotify`
|
||||||
|
- `reason` - The reason for the game ending.
|
||||||
|
- `winner_list` - A list of UIDs (ints) of the players who won the game.
|
||||||
|
- `settle_info_list` - HideAndSeekSettleInfo data.
|
||||||
|
- This is a list of players who participated in the game.
|
||||||
|
|
||||||
|
## `HideAndSeekSettleInfo`
|
||||||
|
- `card_list` - A collection of `ExhibitionDisplayInfo`
|
||||||
|
- If unknown: hardcode the specified values. 
|
||||||
|
- These values are repeated during testing.
|
||||||
|
- `uid` - The UID of the player who participated in the game.
|
||||||
|
- `nickname` - The player's nickname.
|
||||||
|
- `head_image` - This value is always `0`.
|
||||||
|
- `online_id` - This value is always blank.
|
||||||
|
- `profile_picture` - `ProfilePicture` object.
|
||||||
|
- `play_index` - Value picked by the server. (use 1)
|
||||||
|
- `stage_type` - The stage type. (inconclusive; TODO)
|
||||||
|
- `cost_time` - The amount of time the player took to complete the game.
|
||||||
|
- `score_list` - A list of player scores.
|
||||||
|
|
||||||
|
## `ExhibitionDisplayInfo`
|
||||||
|
- `id` - The ID of the reward.
|
||||||
|
- `param` - The amount of the reward given.
|
||||||
|
- `detail_param` - This value is *mostly* 0.
|
||||||
|
- This value **matches** param when the reward is of the amount of time spent playing. (participation reward)
|
BIN
docs/events/windtrace/images/defaultexhibitioninfo.png
Normal file
BIN
docs/events/windtrace/images/defaultexhibitioninfo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
docs/events/windtrace/images/multistageplayinfo.png
Normal file
BIN
docs/events/windtrace/images/multistageplayinfo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
docs/events/windtrace/images/pickavatar.png
Normal file
BIN
docs/events/windtrace/images/pickavatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docs/events/windtrace/images/seektime.png
Normal file
BIN
docs/events/windtrace/images/seektime.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
docs/events/windtrace/images/startwindtrace.png
Normal file
BIN
docs/events/windtrace/images/startwindtrace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@ -68,7 +68,7 @@ class MlgmXyysd_Animation_Company_Proxy:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def request(self, flow: http.HTTPFlow) -> None:
|
def request(self, flow: http.HTTPFlow) -> None:
|
||||||
if flow.request.host in self.LIST_DOMAINS:
|
if flow.request.pretty_host in self.LIST_DOMAINS:
|
||||||
if USE_SSL:
|
if USE_SSL:
|
||||||
flow.request.scheme = "https"
|
flow.request.scheme = "https"
|
||||||
else:
|
else:
|
||||||
|
@ -36,21 +36,21 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
int getEntityIdList(int index);
|
int getEntityIdList(int index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The enum numeric value on the wire for cAKDDMKAIMD.
|
* @return The enum numeric value on the wire for status.
|
||||||
*/
|
*/
|
||||||
int getCAKDDMKAIMDValue();
|
int getStatusValue();
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The cAKDDMKAIMD.
|
* @return The status.
|
||||||
*/
|
*/
|
||||||
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getCAKDDMKAIMD();
|
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>uint32 JHFNDBIHLNB = 8;</code>
|
* <code>uint32 seed_id = 8;</code>
|
||||||
* @return The jHFNDBIHLNB.
|
* @return The seedId.
|
||||||
*/
|
*/
|
||||||
int getJHFNDBIHLNB();
|
int getSeedId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>fixed32 end_time = 14;</code>
|
* <code>fixed32 end_time = 14;</code>
|
||||||
@ -59,10 +59,10 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
int getEndTime();
|
int getEndTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>uint32 KHFGOPCOAGM = 3;</code>
|
* <code>uint32 gather_point_type = 3;</code>
|
||||||
* @return The kHFGOPCOAGM.
|
* @return The gatherPointType.
|
||||||
*/
|
*/
|
||||||
int getKHFGOPCOAGM();
|
int getGatherPointType();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -82,7 +82,7 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
}
|
}
|
||||||
private HomePlantSubFieldData() {
|
private HomePlantSubFieldData() {
|
||||||
entityIdList_ = emptyIntList();
|
entityIdList_ = emptyIntList();
|
||||||
cAKDDMKAIMD_ = 0;
|
status_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
@ -118,7 +118,7 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
break;
|
break;
|
||||||
case 24: {
|
case 24: {
|
||||||
|
|
||||||
kHFGOPCOAGM_ = input.readUInt32();
|
gatherPointType_ = input.readUInt32();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 48: {
|
case 48: {
|
||||||
@ -145,12 +145,12 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
case 56: {
|
case 56: {
|
||||||
int rawValue = input.readEnum();
|
int rawValue = input.readEnum();
|
||||||
|
|
||||||
cAKDDMKAIMD_ = rawValue;
|
status_ = rawValue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 64: {
|
case 64: {
|
||||||
|
|
||||||
jHFNDBIHLNB_ = input.readUInt32();
|
seedId_ = input.readUInt32();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 117: {
|
case 117: {
|
||||||
@ -221,34 +221,34 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
}
|
}
|
||||||
private int entityIdListMemoizedSerializedSize = -1;
|
private int entityIdListMemoizedSerializedSize = -1;
|
||||||
|
|
||||||
public static final int CAKDDMKAIMD_FIELD_NUMBER = 7;
|
public static final int STATUS_FIELD_NUMBER = 7;
|
||||||
private int cAKDDMKAIMD_;
|
private int status_;
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The enum numeric value on the wire for cAKDDMKAIMD.
|
* @return The enum numeric value on the wire for status.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override public int getCAKDDMKAIMDValue() {
|
@java.lang.Override public int getStatusValue() {
|
||||||
return cAKDDMKAIMD_;
|
return status_;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The cAKDDMKAIMD.
|
* @return The status.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getCAKDDMKAIMD() {
|
@java.lang.Override public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getStatus() {
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus result = emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.valueOf(cAKDDMKAIMD_);
|
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus result = emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.valueOf(status_);
|
||||||
return result == null ? emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.UNRECOGNIZED : result;
|
return result == null ? emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.UNRECOGNIZED : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int JHFNDBIHLNB_FIELD_NUMBER = 8;
|
public static final int SEED_ID_FIELD_NUMBER = 8;
|
||||||
private int jHFNDBIHLNB_;
|
private int seedId_;
|
||||||
/**
|
/**
|
||||||
* <code>uint32 JHFNDBIHLNB = 8;</code>
|
* <code>uint32 seed_id = 8;</code>
|
||||||
* @return The jHFNDBIHLNB.
|
* @return The seedId.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public int getJHFNDBIHLNB() {
|
public int getSeedId() {
|
||||||
return jHFNDBIHLNB_;
|
return seedId_;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int END_TIME_FIELD_NUMBER = 14;
|
public static final int END_TIME_FIELD_NUMBER = 14;
|
||||||
@ -262,15 +262,15 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
return endTime_;
|
return endTime_;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int KHFGOPCOAGM_FIELD_NUMBER = 3;
|
public static final int GATHER_POINT_TYPE_FIELD_NUMBER = 3;
|
||||||
private int kHFGOPCOAGM_;
|
private int gatherPointType_;
|
||||||
/**
|
/**
|
||||||
* <code>uint32 KHFGOPCOAGM = 3;</code>
|
* <code>uint32 gather_point_type = 3;</code>
|
||||||
* @return The kHFGOPCOAGM.
|
* @return The gatherPointType.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public int getKHFGOPCOAGM() {
|
public int getGatherPointType() {
|
||||||
return kHFGOPCOAGM_;
|
return gatherPointType_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte memoizedIsInitialized = -1;
|
private byte memoizedIsInitialized = -1;
|
||||||
@ -288,8 +288,8 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
public void writeTo(com.google.protobuf.CodedOutputStream output)
|
public void writeTo(com.google.protobuf.CodedOutputStream output)
|
||||||
throws java.io.IOException {
|
throws java.io.IOException {
|
||||||
getSerializedSize();
|
getSerializedSize();
|
||||||
if (kHFGOPCOAGM_ != 0) {
|
if (gatherPointType_ != 0) {
|
||||||
output.writeUInt32(3, kHFGOPCOAGM_);
|
output.writeUInt32(3, gatherPointType_);
|
||||||
}
|
}
|
||||||
if (getEntityIdListList().size() > 0) {
|
if (getEntityIdListList().size() > 0) {
|
||||||
output.writeUInt32NoTag(50);
|
output.writeUInt32NoTag(50);
|
||||||
@ -298,11 +298,11 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
for (int i = 0; i < entityIdList_.size(); i++) {
|
for (int i = 0; i < entityIdList_.size(); i++) {
|
||||||
output.writeUInt32NoTag(entityIdList_.getInt(i));
|
output.writeUInt32NoTag(entityIdList_.getInt(i));
|
||||||
}
|
}
|
||||||
if (cAKDDMKAIMD_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
|
if (status_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
|
||||||
output.writeEnum(7, cAKDDMKAIMD_);
|
output.writeEnum(7, status_);
|
||||||
}
|
}
|
||||||
if (jHFNDBIHLNB_ != 0) {
|
if (seedId_ != 0) {
|
||||||
output.writeUInt32(8, jHFNDBIHLNB_);
|
output.writeUInt32(8, seedId_);
|
||||||
}
|
}
|
||||||
if (endTime_ != 0) {
|
if (endTime_ != 0) {
|
||||||
output.writeFixed32(14, endTime_);
|
output.writeFixed32(14, endTime_);
|
||||||
@ -316,9 +316,9 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
if (size != -1) return size;
|
if (size != -1) return size;
|
||||||
|
|
||||||
size = 0;
|
size = 0;
|
||||||
if (kHFGOPCOAGM_ != 0) {
|
if (gatherPointType_ != 0) {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeUInt32Size(3, kHFGOPCOAGM_);
|
.computeUInt32Size(3, gatherPointType_);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int dataSize = 0;
|
int dataSize = 0;
|
||||||
@ -334,13 +334,13 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
}
|
}
|
||||||
entityIdListMemoizedSerializedSize = dataSize;
|
entityIdListMemoizedSerializedSize = dataSize;
|
||||||
}
|
}
|
||||||
if (cAKDDMKAIMD_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
|
if (status_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeEnumSize(7, cAKDDMKAIMD_);
|
.computeEnumSize(7, status_);
|
||||||
}
|
}
|
||||||
if (jHFNDBIHLNB_ != 0) {
|
if (seedId_ != 0) {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeUInt32Size(8, jHFNDBIHLNB_);
|
.computeUInt32Size(8, seedId_);
|
||||||
}
|
}
|
||||||
if (endTime_ != 0) {
|
if (endTime_ != 0) {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
@ -363,13 +363,13 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
|
|
||||||
if (!getEntityIdListList()
|
if (!getEntityIdListList()
|
||||||
.equals(other.getEntityIdListList())) return false;
|
.equals(other.getEntityIdListList())) return false;
|
||||||
if (cAKDDMKAIMD_ != other.cAKDDMKAIMD_) return false;
|
if (status_ != other.status_) return false;
|
||||||
if (getJHFNDBIHLNB()
|
if (getSeedId()
|
||||||
!= other.getJHFNDBIHLNB()) return false;
|
!= other.getSeedId()) return false;
|
||||||
if (getEndTime()
|
if (getEndTime()
|
||||||
!= other.getEndTime()) return false;
|
!= other.getEndTime()) return false;
|
||||||
if (getKHFGOPCOAGM()
|
if (getGatherPointType()
|
||||||
!= other.getKHFGOPCOAGM()) return false;
|
!= other.getGatherPointType()) return false;
|
||||||
if (!unknownFields.equals(other.unknownFields)) return false;
|
if (!unknownFields.equals(other.unknownFields)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -385,14 +385,14 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
hash = (37 * hash) + ENTITY_ID_LIST_FIELD_NUMBER;
|
hash = (37 * hash) + ENTITY_ID_LIST_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + getEntityIdListList().hashCode();
|
hash = (53 * hash) + getEntityIdListList().hashCode();
|
||||||
}
|
}
|
||||||
hash = (37 * hash) + CAKDDMKAIMD_FIELD_NUMBER;
|
hash = (37 * hash) + STATUS_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + cAKDDMKAIMD_;
|
hash = (53 * hash) + status_;
|
||||||
hash = (37 * hash) + JHFNDBIHLNB_FIELD_NUMBER;
|
hash = (37 * hash) + SEED_ID_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + getJHFNDBIHLNB();
|
hash = (53 * hash) + getSeedId();
|
||||||
hash = (37 * hash) + END_TIME_FIELD_NUMBER;
|
hash = (37 * hash) + END_TIME_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + getEndTime();
|
hash = (53 * hash) + getEndTime();
|
||||||
hash = (37 * hash) + KHFGOPCOAGM_FIELD_NUMBER;
|
hash = (37 * hash) + GATHER_POINT_TYPE_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + getKHFGOPCOAGM();
|
hash = (53 * hash) + getGatherPointType();
|
||||||
hash = (29 * hash) + unknownFields.hashCode();
|
hash = (29 * hash) + unknownFields.hashCode();
|
||||||
memoizedHashCode = hash;
|
memoizedHashCode = hash;
|
||||||
return hash;
|
return hash;
|
||||||
@ -532,13 +532,13 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
super.clear();
|
super.clear();
|
||||||
entityIdList_ = emptyIntList();
|
entityIdList_ = emptyIntList();
|
||||||
bitField0_ = (bitField0_ & ~0x00000001);
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
cAKDDMKAIMD_ = 0;
|
status_ = 0;
|
||||||
|
|
||||||
jHFNDBIHLNB_ = 0;
|
seedId_ = 0;
|
||||||
|
|
||||||
endTime_ = 0;
|
endTime_ = 0;
|
||||||
|
|
||||||
kHFGOPCOAGM_ = 0;
|
gatherPointType_ = 0;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -572,10 +572,10 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
bitField0_ = (bitField0_ & ~0x00000001);
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
}
|
}
|
||||||
result.entityIdList_ = entityIdList_;
|
result.entityIdList_ = entityIdList_;
|
||||||
result.cAKDDMKAIMD_ = cAKDDMKAIMD_;
|
result.status_ = status_;
|
||||||
result.jHFNDBIHLNB_ = jHFNDBIHLNB_;
|
result.seedId_ = seedId_;
|
||||||
result.endTime_ = endTime_;
|
result.endTime_ = endTime_;
|
||||||
result.kHFGOPCOAGM_ = kHFGOPCOAGM_;
|
result.gatherPointType_ = gatherPointType_;
|
||||||
onBuilt();
|
onBuilt();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -634,17 +634,17 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
}
|
}
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
if (other.cAKDDMKAIMD_ != 0) {
|
if (other.status_ != 0) {
|
||||||
setCAKDDMKAIMDValue(other.getCAKDDMKAIMDValue());
|
setStatusValue(other.getStatusValue());
|
||||||
}
|
}
|
||||||
if (other.getJHFNDBIHLNB() != 0) {
|
if (other.getSeedId() != 0) {
|
||||||
setJHFNDBIHLNB(other.getJHFNDBIHLNB());
|
setSeedId(other.getSeedId());
|
||||||
}
|
}
|
||||||
if (other.getEndTime() != 0) {
|
if (other.getEndTime() != 0) {
|
||||||
setEndTime(other.getEndTime());
|
setEndTime(other.getEndTime());
|
||||||
}
|
}
|
||||||
if (other.getKHFGOPCOAGM() != 0) {
|
if (other.getGatherPointType() != 0) {
|
||||||
setKHFGOPCOAGM(other.getKHFGOPCOAGM());
|
setGatherPointType(other.getGatherPointType());
|
||||||
}
|
}
|
||||||
this.mergeUnknownFields(other.unknownFields);
|
this.mergeUnknownFields(other.unknownFields);
|
||||||
onChanged();
|
onChanged();
|
||||||
@ -755,87 +755,87 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int cAKDDMKAIMD_ = 0;
|
private int status_ = 0;
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The enum numeric value on the wire for cAKDDMKAIMD.
|
* @return The enum numeric value on the wire for status.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override public int getCAKDDMKAIMDValue() {
|
@java.lang.Override public int getStatusValue() {
|
||||||
return cAKDDMKAIMD_;
|
return status_;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @param value The enum numeric value on the wire for cAKDDMKAIMD to set.
|
* @param value The enum numeric value on the wire for status to set.
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder setCAKDDMKAIMDValue(int value) {
|
public Builder setStatusValue(int value) {
|
||||||
|
|
||||||
cAKDDMKAIMD_ = value;
|
status_ = value;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return The cAKDDMKAIMD.
|
* @return The status.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getCAKDDMKAIMD() {
|
public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getStatus() {
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus result = emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.valueOf(cAKDDMKAIMD_);
|
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus result = emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.valueOf(status_);
|
||||||
return result == null ? emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.UNRECOGNIZED : result;
|
return result == null ? emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.UNRECOGNIZED : result;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @param value The cAKDDMKAIMD to set.
|
* @param value The status to set.
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder setCAKDDMKAIMD(emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus value) {
|
public Builder setStatus(emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
|
|
||||||
cAKDDMKAIMD_ = value.getNumber();
|
status_ = value.getNumber();
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
|
* <code>.HomePlantFieldStatus status = 7;</code>
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder clearCAKDDMKAIMD() {
|
public Builder clearStatus() {
|
||||||
|
|
||||||
cAKDDMKAIMD_ = 0;
|
status_ = 0;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int jHFNDBIHLNB_ ;
|
private int seedId_ ;
|
||||||
/**
|
/**
|
||||||
* <code>uint32 JHFNDBIHLNB = 8;</code>
|
* <code>uint32 seed_id = 8;</code>
|
||||||
* @return The jHFNDBIHLNB.
|
* @return The seedId.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public int getJHFNDBIHLNB() {
|
public int getSeedId() {
|
||||||
return jHFNDBIHLNB_;
|
return seedId_;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>uint32 JHFNDBIHLNB = 8;</code>
|
* <code>uint32 seed_id = 8;</code>
|
||||||
* @param value The jHFNDBIHLNB to set.
|
* @param value The seedId to set.
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder setJHFNDBIHLNB(int value) {
|
public Builder setSeedId(int value) {
|
||||||
|
|
||||||
jHFNDBIHLNB_ = value;
|
seedId_ = value;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>uint32 JHFNDBIHLNB = 8;</code>
|
* <code>uint32 seed_id = 8;</code>
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder clearJHFNDBIHLNB() {
|
public Builder clearSeedId() {
|
||||||
|
|
||||||
jHFNDBIHLNB_ = 0;
|
seedId_ = 0;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -871,33 +871,33 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int kHFGOPCOAGM_ ;
|
private int gatherPointType_ ;
|
||||||
/**
|
/**
|
||||||
* <code>uint32 KHFGOPCOAGM = 3;</code>
|
* <code>uint32 gather_point_type = 3;</code>
|
||||||
* @return The kHFGOPCOAGM.
|
* @return The gatherPointType.
|
||||||
*/
|
*/
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public int getKHFGOPCOAGM() {
|
public int getGatherPointType() {
|
||||||
return kHFGOPCOAGM_;
|
return gatherPointType_;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>uint32 KHFGOPCOAGM = 3;</code>
|
* <code>uint32 gather_point_type = 3;</code>
|
||||||
* @param value The kHFGOPCOAGM to set.
|
* @param value The gatherPointType to set.
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder setKHFGOPCOAGM(int value) {
|
public Builder setGatherPointType(int value) {
|
||||||
|
|
||||||
kHFGOPCOAGM_ = value;
|
gatherPointType_ = value;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* <code>uint32 KHFGOPCOAGM = 3;</code>
|
* <code>uint32 gather_point_type = 3;</code>
|
||||||
* @return This builder for chaining.
|
* @return This builder for chaining.
|
||||||
*/
|
*/
|
||||||
public Builder clearKHFGOPCOAGM() {
|
public Builder clearGatherPointType() {
|
||||||
|
|
||||||
kHFGOPCOAGM_ = 0;
|
gatherPointType_ = 0;
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -969,12 +969,12 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
static {
|
static {
|
||||||
java.lang.String[] descriptorData = {
|
java.lang.String[] descriptorData = {
|
||||||
"\n\033HomePlantSubFieldData.proto\032\032HomePlant" +
|
"\n\033HomePlantSubFieldData.proto\032\032HomePlant" +
|
||||||
"FieldStatus.proto\"\227\001\n\025HomePlantSubFieldD" +
|
"FieldStatus.proto\"\224\001\n\025HomePlantSubFieldD" +
|
||||||
"ata\022\026\n\016entity_id_list\030\006 \003(\r\022*\n\013CAKDDMKAI" +
|
"ata\022\026\n\016entity_id_list\030\006 \003(\r\022%\n\006status\030\007 " +
|
||||||
"MD\030\007 \001(\0162\025.HomePlantFieldStatus\022\023\n\013JHFND" +
|
"\001(\0162\025.HomePlantFieldStatus\022\017\n\007seed_id\030\010 " +
|
||||||
"BIHLNB\030\010 \001(\r\022\020\n\010end_time\030\016 \001(\007\022\023\n\013KHFGOP" +
|
"\001(\r\022\020\n\010end_time\030\016 \001(\007\022\031\n\021gather_point_ty" +
|
||||||
"COAGM\030\003 \001(\rB\033\n\031emu.grasscutter.net.proto" +
|
"pe\030\003 \001(\rB\033\n\031emu.grasscutter.net.protob\006p" +
|
||||||
"b\006proto3"
|
"roto3"
|
||||||
};
|
};
|
||||||
descriptor = com.google.protobuf.Descriptors.FileDescriptor
|
descriptor = com.google.protobuf.Descriptors.FileDescriptor
|
||||||
.internalBuildGeneratedFileFrom(descriptorData,
|
.internalBuildGeneratedFileFrom(descriptorData,
|
||||||
@ -986,7 +986,7 @@ public final class HomePlantSubFieldDataOuterClass {
|
|||||||
internal_static_HomePlantSubFieldData_fieldAccessorTable = new
|
internal_static_HomePlantSubFieldData_fieldAccessorTable = new
|
||||||
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
|
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
|
||||||
internal_static_HomePlantSubFieldData_descriptor,
|
internal_static_HomePlantSubFieldData_descriptor,
|
||||||
new java.lang.String[] { "EntityIdList", "CAKDDMKAIMD", "JHFNDBIHLNB", "EndTime", "KHFGOPCOAGM", });
|
new java.lang.String[] { "EntityIdList", "Status", "SeedId", "EndTime", "GatherPointType", });
|
||||||
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.getDescriptor();
|
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.getDescriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,32 @@
|
|||||||
# Handbook Data
|
# Handbook Data
|
||||||
Use Grasscutter's dumpers to generate the data to put here.
|
Use Grasscutter's dumpers to generate the data to put here.
|
||||||
|
|
||||||
|
# Generating Data
|
||||||
|
|
||||||
|
When you have Grasscutter set up, you can use the following commands to generate the data:
|
||||||
|
- Commands - `grasscutter.jar -dump=commands,en-us`
|
||||||
|
- Items - `grasscutter.jar -dump=items,EN`
|
||||||
|
- Avatars - `grasscutter.jar -dump=avatars,EN`
|
||||||
|
- Quests - `grasscutter.jar -dump=quests,EN`
|
||||||
|
- Entities - `grasscutter.jar -dump=entities,en-us`
|
||||||
|
- Areas - `grasscutter.jar -dump=areas,EN`
|
||||||
|
- Scenes - `grasscutter.jar -dump=scenes,en-us`
|
||||||
|
|
||||||
|
Grasscutter being "set up" means:
|
||||||
|
- A Java runtime is installed
|
||||||
|
- Resources are provided in the working directory
|
||||||
|
|
||||||
|
## Language Locales
|
||||||
|
|
||||||
|
You can replace `en-us` or `EN` using the language locale which matches the format.
|
||||||
|
|
||||||
|
| Grasscutter Language Locale | Handbook Language Locale |
|
||||||
|
|-----------------------------|--------------------------|
|
||||||
|
| en-us | EN |
|
||||||
|
|
||||||
|
|
||||||
## Files Required
|
## Files Required
|
||||||
- `mainquests.csv'
|
- `mainquests.csv`
|
||||||
- `commands.json`
|
- `commands.json`
|
||||||
- `entities.csv`
|
- `entities.csv`
|
||||||
- `avatars.csv`
|
- `avatars.csv`
|
||||||
|
@ -158,6 +158,8 @@ public final class Grasscutter {
|
|||||||
|
|
||||||
// Generate handbooks.
|
// Generate handbooks.
|
||||||
Tools.createGmHandbooks(false);
|
Tools.createGmHandbooks(false);
|
||||||
|
// Generate gacha mappings.
|
||||||
|
Tools.generateGachaMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start servers.
|
// Start servers.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package emu.grasscutter.command;
|
package emu.grasscutter.command;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.world.Position;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.regex.*;
|
import java.util.regex.*;
|
||||||
@ -54,4 +55,78 @@ public class CommandHelpers {
|
|||||||
});
|
});
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float parseRelative(String input, Float current) {
|
||||||
|
if (input.contains("~")) { // Relative
|
||||||
|
if (!input.equals("~")) { // Relative with offset
|
||||||
|
current += Float.parseFloat(input.replace("~", ""));
|
||||||
|
} // Else no offset, no modification
|
||||||
|
} else { // Absolute
|
||||||
|
current = Float.parseFloat(input);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Position parsePosition(
|
||||||
|
String inputX, String inputY, String inputZ, Position curPos, Position curRot) {
|
||||||
|
Position offset = new Position();
|
||||||
|
Position target = new Position(curPos);
|
||||||
|
if (inputX.contains("~")) { // Relative
|
||||||
|
if (!inputX.equals("~")) { // Relative with offset
|
||||||
|
target.addX(Float.parseFloat(inputX.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputX.contains("^")) {
|
||||||
|
if (!inputX.equals("^")) {
|
||||||
|
offset.setX(Float.parseFloat(inputX.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setX(Float.parseFloat(inputX));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputY.contains("~")) { // Relative
|
||||||
|
if (!inputY.equals("~")) { // Relative with offset
|
||||||
|
target.addY(Float.parseFloat(inputY.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputY.contains("^")) {
|
||||||
|
if (!inputY.equals("^")) {
|
||||||
|
offset.setY(Float.parseFloat(inputY.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setY(Float.parseFloat(inputY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputZ.contains("~")) { // Relative
|
||||||
|
if (!inputZ.equals("~")) { // Relative with offset
|
||||||
|
target.addZ(Float.parseFloat(inputZ.replace("~", "")));
|
||||||
|
}
|
||||||
|
} else if (inputZ.contains("^")) {
|
||||||
|
if (!inputZ.equals("^")) {
|
||||||
|
offset.setZ(Float.parseFloat(inputZ.replace("^", "")));
|
||||||
|
}
|
||||||
|
} else { // Absolute
|
||||||
|
target.setZ(Float.parseFloat(inputZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offset.equal3d(Position.ZERO)) {
|
||||||
|
return calculateOffset(target, curRot, offset);
|
||||||
|
} else {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Position calculateOffset(Position pos, Position rot, Position offset) {
|
||||||
|
// Degrees to radians
|
||||||
|
float angleZ = (float) Math.toRadians(rot.getY());
|
||||||
|
float angleX = (float) Math.toRadians(rot.getY() + 90);
|
||||||
|
|
||||||
|
// Calculate offset based on current position and rotation
|
||||||
|
return new Position(
|
||||||
|
pos.getX()
|
||||||
|
+ offset.getZ() * (float) Math.sin(angleZ)
|
||||||
|
+ offset.getX() * (float) Math.sin(angleX),
|
||||||
|
pos.getY() + offset.getY(),
|
||||||
|
pos.getZ()
|
||||||
|
+ offset.getZ() * (float) Math.cos(angleZ)
|
||||||
|
+ offset.getX() * (float) Math.cos(angleX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,12 @@ public final class DebugCommand implements CommandHandler {
|
|||||||
|
|
||||||
var scene = sender.getScene();
|
var scene = sender.getScene();
|
||||||
var entityId = Integer.parseInt(args.get(0));
|
var entityId = Integer.parseInt(args.get(0));
|
||||||
|
// TODO Might want to allow groupId specification,
|
||||||
|
// because there can be more than one entity with
|
||||||
|
// the given config ID.
|
||||||
var entity =
|
var entity =
|
||||||
args.size() > 1 && args.get(1).equals("config")
|
args.size() > 1 && args.get(1).equals("config")
|
||||||
? scene.getEntityByConfigId(entityId)
|
? scene.getFirstEntityByConfigId(entityId)
|
||||||
: scene.getEntityById(entityId);
|
: scene.getEntityById(entityId);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
sender.dropMessage("Entity not found.");
|
sender.dropMessage("Entity not found.");
|
||||||
|
@ -33,7 +33,7 @@ public final class EnterDungeonCommand implements CommandHandler {
|
|||||||
targetPlayer
|
targetPlayer
|
||||||
.getServer()
|
.getServer()
|
||||||
.getDungeonSystem()
|
.getDungeonSystem()
|
||||||
.enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId);
|
.enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId, true);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
|
@ -51,7 +51,10 @@ public final class EntityCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
param.scene = targetPlayer.getScene();
|
param.scene = targetPlayer.getScene();
|
||||||
var entity = param.scene.getEntityByConfigId(param.configId);
|
// TODO Might want to allow groupId specification,
|
||||||
|
// because there can be more than one entity with
|
||||||
|
// the given config ID.
|
||||||
|
var entity = param.scene.getFirstEntityByConfigId(param.configId);
|
||||||
|
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error"));
|
CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error"));
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package emu.grasscutter.command.commands;
|
||||||
|
|
||||||
|
import emu.grasscutter.command.*;
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.data.excels.scene.SceneTagData;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.server.packet.send.*;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import java.util.*;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
label = "setSceneTag",
|
||||||
|
aliases = {"tag"},
|
||||||
|
usage = {"<add|remove|unlockall|reset> <sceneTagId>"},
|
||||||
|
permission = "player.setscenetag",
|
||||||
|
permissionTargeted = "player.setscenetag.others")
|
||||||
|
public final class SetSceneTagCommand implements CommandHandler {
|
||||||
|
private final Int2ObjectMap<SceneTagData> sceneTagData = GameData.getSceneTagDataMap();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
sendUsageMessage(sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
val actionStr = args.get(0).toLowerCase();
|
||||||
|
var value = -1;
|
||||||
|
|
||||||
|
if (args.size() > 1) {
|
||||||
|
try {
|
||||||
|
value = Integer.parseInt(args.get(1));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (actionStr.equals("unlockall")) {
|
||||||
|
unlockAllSceneTags(targetPlayer);
|
||||||
|
return;
|
||||||
|
} else if (actionStr.equals("reset") || actionStr.equals("restore")) {
|
||||||
|
resetAllSceneTags(targetPlayer);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val userVal = value;
|
||||||
|
|
||||||
|
var sceneData =
|
||||||
|
sceneTagData.values().stream().filter(sceneTag -> sceneTag.getId() == userVal).findFirst();
|
||||||
|
if (sceneData.isEmpty()) {
|
||||||
|
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int scene = sceneData.get().getSceneId();
|
||||||
|
|
||||||
|
switch (actionStr) {
|
||||||
|
case "add", "set" -> addSceneTag(targetPlayer, scene, value);
|
||||||
|
case "remove", "del" -> removeSceneTag(targetPlayer, scene, value);
|
||||||
|
default -> CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", value, actionStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSceneTag(Player targetPlayer, int scene, int value) {
|
||||||
|
targetPlayer.getProgressManager().addSceneTag(scene, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSceneTag(Player targetPlayer, int scene, int value) {
|
||||||
|
targetPlayer.getProgressManager().delSceneTag(scene, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlockAllSceneTags(Player targetPlayer) {
|
||||||
|
var allData = sceneTagData.values();
|
||||||
|
|
||||||
|
// Add all SceneTags
|
||||||
|
allData.stream()
|
||||||
|
.toList()
|
||||||
|
.forEach(
|
||||||
|
sceneTag -> {
|
||||||
|
targetPlayer
|
||||||
|
.getSceneTags()
|
||||||
|
.computeIfAbsent(sceneTag.getSceneId(), k -> new HashSet<>());
|
||||||
|
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove default SceneTags, as most are "before" or "locked" states
|
||||||
|
allData.stream()
|
||||||
|
.filter(SceneTagData::isDefaultValid)
|
||||||
|
// Only remove for big world as some other scenes only have defaults
|
||||||
|
.filter(sceneTag -> sceneTag.getSceneId() == 3)
|
||||||
|
.forEach(
|
||||||
|
sceneTag -> {
|
||||||
|
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).remove(sceneTag.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setSceneTags(targetPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetAllSceneTags(Player targetPlayer) {
|
||||||
|
targetPlayer.getSceneTags().clear();
|
||||||
|
// targetPlayer.applyStartingSceneTags(); // private
|
||||||
|
GameData.getSceneTagDataMap().values().stream()
|
||||||
|
.filter(SceneTagData::isDefaultValid)
|
||||||
|
.forEach(
|
||||||
|
sceneTag -> {
|
||||||
|
targetPlayer
|
||||||
|
.getSceneTags()
|
||||||
|
.computeIfAbsent(sceneTag.getSceneId(), k -> new HashSet<>());
|
||||||
|
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setSceneTags(targetPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSceneTags(Player targetPlayer) {
|
||||||
|
targetPlayer.sendPacket(new PacketPlayerWorldSceneInfoListNotify(targetPlayer));
|
||||||
|
}
|
||||||
|
}
|
@ -21,9 +21,9 @@ import lombok.Setter;
|
|||||||
label = "spawn",
|
label = "spawn",
|
||||||
aliases = {"drop", "s"},
|
aliases = {"drop", "s"},
|
||||||
usage = {
|
usage = {
|
||||||
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]",
|
||||||
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]",
|
||||||
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>"
|
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] [<x> <y> <z>] [<rotX> <rotY> <rotZ>]"
|
||||||
},
|
},
|
||||||
permission = "server.spawn",
|
permission = "server.spawn",
|
||||||
permissionTargeted = "server.spawn.others")
|
permissionTargeted = "server.spawn.others")
|
||||||
@ -53,14 +53,23 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
|
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Position pos = new Position(targetPlayer.getPosition());
|
||||||
|
Position rot = new Position(targetPlayer.getRotation());
|
||||||
|
|
||||||
switch (args.size()) {
|
switch (args.size()) {
|
||||||
|
case 7:
|
||||||
|
try {
|
||||||
|
rot.setX(CommandHelpers.parseRelative(args.get(4), rot.getX()));
|
||||||
|
rot.setY(CommandHelpers.parseRelative(args.get(5), rot.getY()));
|
||||||
|
rot.setZ(CommandHelpers.parseRelative(args.get(6), rot.getZ()));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
CommandHandler.sendMessage(
|
||||||
|
sender, translate(sender, "commands.execution.argument_error"));
|
||||||
|
} // Fallthrough
|
||||||
case 4:
|
case 4:
|
||||||
try {
|
try {
|
||||||
float x, y, z;
|
pos = CommandHelpers.parsePosition(args.get(1), args.get(2), args.get(3), pos, rot);
|
||||||
x = Float.parseFloat(args.get(1));
|
|
||||||
y = Float.parseFloat(args.get(2));
|
|
||||||
z = Float.parseFloat(args.get(3));
|
|
||||||
param.pos = new Position(x, y, z);
|
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender, translate(sender, "commands.execution.argument_error"));
|
sender, translate(sender, "commands.execution.argument_error"));
|
||||||
@ -77,6 +86,8 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
sendUsageMessage(sender);
|
sendUsageMessage(sender);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
param.pos = pos;
|
||||||
|
param.rot = rot;
|
||||||
|
|
||||||
MonsterData monsterData = GameData.getMonsterDataMap().get(param.id);
|
MonsterData monsterData = GameData.getMonsterDataMap().get(param.id);
|
||||||
GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
|
GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
|
||||||
@ -102,12 +113,8 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI);
|
double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI);
|
||||||
if (param.pos == null) {
|
|
||||||
param.pos = targetPlayer.getPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < param.amount; i++) {
|
for (int i = 0; i < param.amount; i++) {
|
||||||
Position pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
|
pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
|
||||||
GameEntity entity = null;
|
GameEntity entity = null;
|
||||||
if (itemData != null) {
|
if (itemData != null) {
|
||||||
entity = createItem(itemData, param, pos);
|
entity = createItem(itemData, param, pos);
|
||||||
@ -128,12 +135,12 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) {
|
private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) {
|
||||||
return new EntityItem(param.scene, null, itemData, pos, 1, true);
|
return new EntityItem(param.scene, null, itemData, pos, param.rot, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityMonster createMonster(
|
private EntityMonster createMonster(
|
||||||
MonsterData monsterData, SpawnParameters param, Position pos) {
|
MonsterData monsterData, SpawnParameters param, Position pos) {
|
||||||
var entity = new EntityMonster(param.scene, monsterData, pos, param.lvl);
|
var entity = new EntityMonster(param.scene, monsterData, pos, param.rot, param.lvl);
|
||||||
if (param.ai != -1) {
|
if (param.ai != -1) {
|
||||||
entity.setAiId(param.ai);
|
entity.setAiId(param.ai);
|
||||||
}
|
}
|
||||||
@ -144,16 +151,13 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) {
|
GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) {
|
||||||
EntityBaseGadget entity;
|
EntityBaseGadget entity;
|
||||||
if (gadgetData.getType() == EntityType.Vehicle) {
|
if (gadgetData.getType() == EntityType.Vehicle) {
|
||||||
entity =
|
entity = new EntityVehicle(param.scene, targetPlayer, param.id, 0, pos, param.rot);
|
||||||
new EntityVehicle(
|
|
||||||
param.scene, targetPlayer, param.id, 0, pos, targetPlayer.getRotation());
|
|
||||||
} else {
|
} else {
|
||||||
entity = new EntityGadget(param.scene, param.id, pos, targetPlayer.getRotation());
|
entity = new EntityGadget(param.scene, param.id, pos, param.rot);
|
||||||
if (param.state != -1) {
|
if (param.state != -1) {
|
||||||
((EntityGadget) entity).setState(param.state);
|
((EntityGadget) entity).setState(param.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +211,7 @@ public final class SpawnCommand implements CommandHandler {
|
|||||||
@Setter public int def = -1;
|
@Setter public int def = -1;
|
||||||
@Setter public int ai = -1;
|
@Setter public int ai = -1;
|
||||||
@Setter public Position pos = null;
|
@Setter public Position pos = null;
|
||||||
|
@Setter public Position rot = null;
|
||||||
public Scene scene = null;
|
public Scene scene = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,24 +16,10 @@ import java.util.List;
|
|||||||
permissionTargeted = "player.teleport.others")
|
permissionTargeted = "player.teleport.others")
|
||||||
public final class TeleportCommand implements CommandHandler {
|
public final class TeleportCommand implements CommandHandler {
|
||||||
|
|
||||||
private float parseRelative(
|
|
||||||
String input, Float current) { // TODO: Maybe this will be useful elsewhere later
|
|
||||||
if (input.contains("~")) { // Relative
|
|
||||||
if (!input.equals("~")) { // Relative with offset
|
|
||||||
current += Float.parseFloat(input.replace("~", ""));
|
|
||||||
} // Else no offset, no modification
|
|
||||||
} else { // Absolute
|
|
||||||
current = Float.parseFloat(input);
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||||
Position pos = targetPlayer.getPosition();
|
Position pos = new Position(targetPlayer.getPosition());
|
||||||
float x = pos.getX();
|
Position rot = new Position(targetPlayer.getRotation());
|
||||||
float y = pos.getY();
|
|
||||||
float z = pos.getZ();
|
|
||||||
int sceneId = targetPlayer.getSceneId();
|
int sceneId = targetPlayer.getSceneId();
|
||||||
|
|
||||||
switch (args.size()) {
|
switch (args.size()) {
|
||||||
@ -46,9 +32,7 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
} // Fallthrough
|
} // Fallthrough
|
||||||
case 3:
|
case 3:
|
||||||
try {
|
try {
|
||||||
x = this.parseRelative(args.get(0), x);
|
pos = CommandHelpers.parsePosition(args.get(0), args.get(1), args.get(2), pos, rot);
|
||||||
y = this.parseRelative(args.get(1), y);
|
|
||||||
z = this.parseRelative(args.get(2), z);
|
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender, translate(sender, "commands.teleport.invalid_position"));
|
sender, translate(sender, "commands.teleport.invalid_position"));
|
||||||
@ -59,11 +43,10 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Position target_pos = new Position(x, y, z);
|
|
||||||
boolean result =
|
boolean result =
|
||||||
targetPlayer
|
targetPlayer
|
||||||
.getWorld()
|
.getWorld()
|
||||||
.transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, target_pos);
|
.transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, pos);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
|
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
|
||||||
@ -71,7 +54,13 @@ public final class TeleportCommand implements CommandHandler {
|
|||||||
CommandHandler.sendMessage(
|
CommandHandler.sendMessage(
|
||||||
sender,
|
sender,
|
||||||
translate(
|
translate(
|
||||||
sender, "commands.teleport.success", targetPlayer.getNickname(), x, y, z, sceneId));
|
sender,
|
||||||
|
"commands.teleport.success",
|
||||||
|
targetPlayer.getNickname(),
|
||||||
|
pos.getX(),
|
||||||
|
pos.getY(),
|
||||||
|
pos.getZ(),
|
||||||
|
sceneId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,11 @@ public class ConfigContainer {
|
|||||||
* Lua script require system if performance is a concern.
|
* Lua script require system if performance is a concern.
|
||||||
* Version 12 - 'http.startImmediately' was added to control whether the
|
* Version 12 - 'http.startImmediately' was added to control whether the
|
||||||
* HTTP server should start immediately.
|
* HTTP server should start immediately.
|
||||||
|
* Version 13 - 'game.useUniquePacketKey' was added to control whether the
|
||||||
|
* encryption key used for packets is a constant or randomly generated.
|
||||||
*/
|
*/
|
||||||
private static int version() {
|
private static int version() {
|
||||||
return 12;
|
return 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,6 +171,9 @@ public class ConfigContainer {
|
|||||||
/* This is the port used in the default region. */
|
/* This is the port used in the default region. */
|
||||||
public int accessPort = 0;
|
public int accessPort = 0;
|
||||||
|
|
||||||
|
/* Enabling this will generate a unique packet encryption key for each player. */
|
||||||
|
public boolean useUniquePacketKey = true;
|
||||||
|
|
||||||
/* Entities within a certain range will be loaded for the player */
|
/* Entities within a certain range will be loaded for the player */
|
||||||
public int loadEntitiesForPlayerRange = 300;
|
public int loadEntitiesForPlayerRange = 300;
|
||||||
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */
|
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package emu.grasscutter.data;
|
package emu.grasscutter.data;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.server.http.handlers.GachaHandler;
|
|
||||||
import emu.grasscutter.tools.Tools;
|
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
@ -114,8 +112,6 @@ public class DataLoader {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
|
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateGachaMappings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkAndCopyData(String name) {
|
private static void checkAndCopyData(String name) {
|
||||||
@ -131,16 +127,4 @@ public class DataLoader {
|
|||||||
FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
|
FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateGachaMappings() {
|
|
||||||
var path = GachaHandler.getGachaMappingsPath();
|
|
||||||
if (!Files.exists(path)) {
|
|
||||||
try {
|
|
||||||
Grasscutter.getLogger().debug("Creating default '" + path + "' data");
|
|
||||||
Tools.createGachaMappings(path);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,14 @@ public final class GameData {
|
|||||||
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
|
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<CoopChapterData> coopChapterDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<CoopPointData> coopPointDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
@ -286,6 +294,10 @@ public final class GameData {
|
|||||||
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
|
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<HomeWorldEventData> homeWorldEventDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
|
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
@ -273,18 +273,20 @@ public class AbilityModifier implements Serializable {
|
|||||||
|
|
||||||
@SerializedName(
|
@SerializedName(
|
||||||
value = "amount",
|
value = "amount",
|
||||||
alternate = {"PDLLIFICICJ", "cdRatio"})
|
alternate = {"LNFMOCKIAGK", "PDLLIFICICJ", "cdRatio"})
|
||||||
public DynamicFloat amount = DynamicFloat.ZERO;
|
public DynamicFloat amount = DynamicFloat.ZERO;
|
||||||
|
|
||||||
@SerializedName(value = "amountByTargetCurrentHPRatio")
|
@SerializedName(
|
||||||
|
value = "amountByTargetCurrentHPRatio",
|
||||||
|
alternate = {"GMFELAKANEF"})
|
||||||
public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO;
|
public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO;
|
||||||
|
|
||||||
@SerializedName(value = "unused")
|
@SerializedName(value = "unknown2")
|
||||||
public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO;
|
public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO;
|
||||||
|
|
||||||
@SerializedName(
|
@SerializedName(
|
||||||
value = "unknown",
|
value = "amountByCasterMaxHPRatio",
|
||||||
alternate = {"HFNJHOGGFKB", "GEJGGCIOLKN"})
|
alternate = {"PKPBLCNMPIG", "HFNJHOGGFKB", "GEJGGCIOLKN"})
|
||||||
public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
|
public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
|
||||||
|
|
||||||
public DynamicFloat amountByGetDamage = DynamicFloat.ZERO;
|
public DynamicFloat amountByGetDamage = DynamicFloat.ZERO;
|
||||||
@ -292,7 +294,7 @@ public class AbilityModifier implements Serializable {
|
|||||||
@SerializedName(value = "amountByTargetMaxHPRatio")
|
@SerializedName(value = "amountByTargetMaxHPRatio")
|
||||||
public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
|
public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
|
||||||
|
|
||||||
@SerializedName(value = "amountByCasterMaxHPRatio")
|
@SerializedName(value = "unknown1", alternate = "GGLMMJHNGMO")
|
||||||
public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO;
|
public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO;
|
||||||
|
|
||||||
public DynamicFloat limboByTargetMaxHPRatio = DynamicFloat.ZERO;
|
public DynamicFloat limboByTargetMaxHPRatio = DynamicFloat.ZERO;
|
||||||
|
@ -19,6 +19,7 @@ public final class PointData {
|
|||||||
@Getter private Position size;
|
@Getter private Position size;
|
||||||
@Getter private boolean forbidSimpleUnlock;
|
@Getter private boolean forbidSimpleUnlock;
|
||||||
@Getter private boolean unlocked;
|
@Getter private boolean unlocked;
|
||||||
|
@Getter private boolean groupLimit;
|
||||||
|
|
||||||
@SerializedName(
|
@SerializedName(
|
||||||
value = "dungeonIds",
|
value = "dungeonIds",
|
||||||
@ -28,7 +29,7 @@ public final class PointData {
|
|||||||
|
|
||||||
@SerializedName(
|
@SerializedName(
|
||||||
value = "dungeonRandomList",
|
value = "dungeonRandomList",
|
||||||
alternate = {"OIBKFJNBLHO"})
|
alternate = {"GLEKJMEEOMH"})
|
||||||
@Getter
|
@Getter
|
||||||
private int[] dungeonRandomList;
|
private int[] dungeonRandomList;
|
||||||
|
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import emu.grasscutter.data.*;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "CoopChapterExcelConfigData.json")
|
||||||
|
@Getter
|
||||||
|
@Setter // TODO: remove setters next API break
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class CoopChapterData extends GameResource {
|
||||||
|
@Getter(onMethod_ = @Override)
|
||||||
|
int id;
|
||||||
|
|
||||||
|
int avatarId;
|
||||||
|
// int chapterNameTextMapHash;
|
||||||
|
// int coopPageTitleTextMapHash;
|
||||||
|
// int chapterSortId;
|
||||||
|
// int avatarSortId;
|
||||||
|
// String chapterIcon;
|
||||||
|
List<CoopCondition> unlockCond;
|
||||||
|
// int [] unlockCondTips;
|
||||||
|
// int openMaterialId;
|
||||||
|
// int openMaterialNum;
|
||||||
|
// String beginTimeStr;
|
||||||
|
// int confidenceValue;
|
||||||
|
// String pointGraphPath;
|
||||||
|
// Double graphXRatio;
|
||||||
|
// Double graphYRatio;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
private static class CoopCondition {
|
||||||
|
@SerializedName(
|
||||||
|
value = "_condType",
|
||||||
|
alternate = {"condType"})
|
||||||
|
String type = "COOP_COND_NONE";
|
||||||
|
|
||||||
|
@SerializedName(
|
||||||
|
value = "_args",
|
||||||
|
alternate = {"args"})
|
||||||
|
int[] args;
|
||||||
|
}
|
||||||
|
}
|
24
src/main/java/emu/grasscutter/data/excels/CoopPointData.java
Normal file
24
src/main/java/emu/grasscutter/data/excels/CoopPointData.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.*;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "CoopPointExcelConfigData.json")
|
||||||
|
@Getter
|
||||||
|
@Setter // TODO: remove setters next API break
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class CoopPointData extends GameResource {
|
||||||
|
@Getter(onMethod_ = @Override)
|
||||||
|
int id;
|
||||||
|
|
||||||
|
int chapterId;
|
||||||
|
String type;
|
||||||
|
int acceptQuest;
|
||||||
|
int[] postPointList;
|
||||||
|
// int pointNameTextMapHash;
|
||||||
|
// int pointDecTextMapHash;
|
||||||
|
int pointPosId;
|
||||||
|
// long photoMaleHash;
|
||||||
|
// long photoFemaleHash;
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import emu.grasscutter.data.GameResource;
|
||||||
|
import emu.grasscutter.data.ResourceType;
|
||||||
|
import emu.grasscutter.game.home.suite.event.SuiteEventType;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "HomeWorldEventExcelConfigData.json")
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
|
public class HomeWorldEventData extends GameResource {
|
||||||
|
@SerializedName(
|
||||||
|
value = "id",
|
||||||
|
alternate = {"BBEIIPEFDPE"})
|
||||||
|
int id;
|
||||||
|
|
||||||
|
@SerializedName(
|
||||||
|
value = "eventType",
|
||||||
|
alternate = {"JOCKIMECHDP"})
|
||||||
|
SuiteEventType eventType;
|
||||||
|
|
||||||
|
int avatarID;
|
||||||
|
|
||||||
|
@SerializedName(
|
||||||
|
value = "talkId",
|
||||||
|
alternate = {"IGNJAICDFPD"})
|
||||||
|
int talkId;
|
||||||
|
|
||||||
|
int rewardID;
|
||||||
|
|
||||||
|
@SerializedName(
|
||||||
|
value = "suiteId",
|
||||||
|
alternate = {"FEHOKMJPOED"})
|
||||||
|
int suiteId;
|
||||||
|
}
|
@ -22,6 +22,7 @@ public class DungeonData extends GameResource {
|
|||||||
private DungeonInvolveType involveType;
|
private DungeonInvolveType involveType;
|
||||||
@Getter private int limitLevel;
|
@Getter private int limitLevel;
|
||||||
@Getter private int passCond;
|
@Getter private int passCond;
|
||||||
|
@Getter private int passJumpDungeon;
|
||||||
@Getter private int reviveMaxCount;
|
@Getter private int reviveMaxCount;
|
||||||
@Getter private int settleCountdownTime;
|
@Getter private int settleCountdownTime;
|
||||||
@Getter private int failSettleCountdownTime;
|
@Getter private int failSettleCountdownTime;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package emu.grasscutter.data.excels.dungeon;
|
package emu.grasscutter.data.excels.dungeon;
|
||||||
|
|
||||||
import emu.grasscutter.data.*;
|
import emu.grasscutter.data.*;
|
||||||
|
import emu.grasscutter.game.dungeons.enums.*;
|
||||||
|
import java.util.List;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
@ResourceType(name = "DungeonEntryExcelConfigData.json")
|
@ResourceType(name = "DungeonEntryExcelConfigData.json")
|
||||||
@ -12,4 +14,46 @@ public class DungeonEntryData extends GameResource {
|
|||||||
|
|
||||||
private int dungeonEntryId;
|
private int dungeonEntryId;
|
||||||
private int sceneId;
|
private int sceneId;
|
||||||
|
private DungunEntryType type;
|
||||||
|
private DungeonEntryCondCombType condComb;
|
||||||
|
private List<DungeonEntryCondition> satisfiedCond;
|
||||||
|
|
||||||
|
public static class DungeonEntryCondition {
|
||||||
|
private DungeonEntrySatisfiedConditionType type;
|
||||||
|
private int param1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DungunEntryType getType() {
|
||||||
|
if (type == null) {
|
||||||
|
return DungunEntryType.DUNGEN_ENTRY_TYPE_NONE;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DungeonEntryCondCombType getCondComb() {
|
||||||
|
if (condComb == null) {
|
||||||
|
return DungeonEntryCondCombType.DUNGEON_ENTRY_COND_COMB_NONE;
|
||||||
|
}
|
||||||
|
return condComb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLevelCondition() {
|
||||||
|
for (var cond : satisfiedCond) {
|
||||||
|
if (cond.type != null
|
||||||
|
&& cond.type.equals(DungeonEntrySatisfiedConditionType.DUNGEON_ENTRY_CONDITION_LEVEL)) {
|
||||||
|
return cond.param1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuestCondition() {
|
||||||
|
for (var cond : satisfiedCond) {
|
||||||
|
if (cond.type != null
|
||||||
|
&& cond.type.equals(DungeonEntrySatisfiedConditionType.DUNGEON_ENTRY_CONDITION_QUEST)) {
|
||||||
|
return cond.param1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,82 @@
|
|||||||
package emu.grasscutter.data.excels.tower;
|
package emu.grasscutter.data.excels.tower;
|
||||||
|
|
||||||
import emu.grasscutter.data.*;
|
import emu.grasscutter.data.*;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
@ResourceType(name = "TowerLevelExcelConfigData.json")
|
@ResourceType(name = "TowerLevelExcelConfigData.json")
|
||||||
|
@Getter
|
||||||
public class TowerLevelData extends GameResource {
|
public class TowerLevelData extends GameResource {
|
||||||
|
|
||||||
private int levelId;
|
private int levelId;
|
||||||
private int levelIndex;
|
private int levelIndex;
|
||||||
private int levelGroupId;
|
private int levelGroupId;
|
||||||
private int dungeonId;
|
private int dungeonId;
|
||||||
|
private List<TowerLevelCond> conds;
|
||||||
|
|
||||||
|
public static class TowerLevelCond {
|
||||||
|
private TowerCondType towerCondType;
|
||||||
|
private List<Integer> argumentList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TowerCondType {
|
||||||
|
TOWER_COND_NONE,
|
||||||
|
TOWER_COND_CHALLENGE_LEFT_TIME_MORE_THAN,
|
||||||
|
TOWER_COND_LEFT_HP_GREATER_THAN
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not actual data in TowerLevelExcelConfigData.
|
||||||
|
// Just packaging condition parameters for convenience.
|
||||||
|
@Getter
|
||||||
|
public class TowerCondTimeParams {
|
||||||
|
private int param1;
|
||||||
|
private int minimumTimeInSeconds;
|
||||||
|
|
||||||
|
public TowerCondTimeParams(int param1, int minimumTimeInSeconds) {
|
||||||
|
this.param1 = param1;
|
||||||
|
this.minimumTimeInSeconds = minimumTimeInSeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class TowerCondHpParams {
|
||||||
|
private int sceneId;
|
||||||
|
private int configId;
|
||||||
|
private int minimumHpPercentage;
|
||||||
|
|
||||||
|
public TowerCondHpParams(int sceneId, int configId, int minimumHpPercentage) {
|
||||||
|
this.sceneId = sceneId;
|
||||||
|
this.configId = configId;
|
||||||
|
this.minimumHpPercentage = minimumHpPercentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return this.getLevelId();
|
return this.getLevelId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelId() {
|
public TowerCondType getCondType(int star) {
|
||||||
return levelId;
|
if (star < 0 || conds == null || star >= conds.size()) {
|
||||||
|
return TowerCondType.TOWER_COND_NONE;
|
||||||
|
}
|
||||||
|
var condType = conds.get(star).towerCondType;
|
||||||
|
return condType == null ? TowerCondType.TOWER_COND_NONE : condType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelGroupId() {
|
public TowerCondTimeParams getTimeCond(int star) {
|
||||||
return levelGroupId;
|
if (star < 0 || conds == null || star >= conds.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var params = conds.get(star).argumentList;
|
||||||
|
return new TowerCondTimeParams(params.get(0), params.get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelIndex() {
|
public TowerCondHpParams getHpCond(int star) {
|
||||||
return levelIndex;
|
if (star < 0 || conds == null || star >= conds.size()) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
public int getDungeonId() {
|
var params = conds.get(star).argumentList;
|
||||||
return dungeonId;
|
return new TowerCondHpParams(params.get(0), params.get(1), params.get(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,8 @@ public interface HandbookActions {
|
|||||||
|
|
||||||
// Create the entity.
|
// Create the entity.
|
||||||
for (var i = 1; i <= request.getAmount(); i++) {
|
for (var i = 1; i <= request.getAmount(); i++) {
|
||||||
var entity = new EntityMonster(scene, entityData, player.getPosition(), level);
|
var entity =
|
||||||
|
new EntityMonster(scene, entityData, player.getPosition(), player.getRotation(), level);
|
||||||
scene.addEntity(entity);
|
scene.addEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import emu.grasscutter.data.binout.*;
|
|||||||
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
||||||
import emu.grasscutter.game.ability.actions.*;
|
import emu.grasscutter.game.ability.actions.*;
|
||||||
import emu.grasscutter.game.ability.mixins.*;
|
import emu.grasscutter.game.ability.mixins.*;
|
||||||
|
import emu.grasscutter.game.entity.EntityAvatar;
|
||||||
import emu.grasscutter.game.entity.GameEntity;
|
import emu.grasscutter.game.entity.GameEntity;
|
||||||
import emu.grasscutter.game.player.*;
|
import emu.grasscutter.game.player.*;
|
||||||
import emu.grasscutter.game.props.FightProperty;
|
import emu.grasscutter.game.props.FightProperty;
|
||||||
@ -562,6 +563,14 @@ public final class AbilityManager extends BasePlayerManager {
|
|||||||
if (killState.getKilled()) {
|
if (killState.getKilled()) {
|
||||||
scene.killEntity(entity);
|
scene.killEntity(entity);
|
||||||
} else if (!entity.isAlive()) {
|
} else if (!entity.isAlive()) {
|
||||||
|
if (entity instanceof EntityAvatar) {
|
||||||
|
// TODO Should EntityAvatar act on this invocation?
|
||||||
|
// It bugs revival due to resetting HP to max when
|
||||||
|
// the avatar should just stay dead.
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.trace("Entity of ID {} is EntityAvatar. Ignoring", invoke.getEntityId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
entity.setFightProperty(
|
entity.setFightProperty(
|
||||||
FightProperty.FIGHT_PROP_CUR_HP,
|
FightProperty.FIGHT_PROP_CUR_HP,
|
||||||
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP));
|
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP));
|
||||||
|
@ -6,6 +6,7 @@ import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
|||||||
import emu.grasscutter.game.ability.Ability;
|
import emu.grasscutter.game.ability.Ability;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
import emu.grasscutter.game.props.FightProperty;
|
import emu.grasscutter.game.props.FightProperty;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
|
||||||
|
|
||||||
@AbilityAction(AbilityModifierAction.Type.HealHP)
|
@AbilityAction(AbilityModifierAction.Type.HealHP)
|
||||||
public final class ActionHealHP extends AbilityActionHandler {
|
public final class ActionHealHP extends AbilityActionHandler {
|
||||||
@ -31,24 +32,25 @@ public final class ActionHealHP extends AbilityActionHandler {
|
|||||||
|
|
||||||
if (owner == null) return false;
|
if (owner == null) return false;
|
||||||
|
|
||||||
ability
|
// Get all properties.
|
||||||
.getAbilitySpecials()
|
var properties = new Object2FloatOpenHashMap<String>();
|
||||||
.forEach((k, v) -> Grasscutter.getLogger().trace(">>> {}: {}", k, v));
|
// Add entity fight properties.
|
||||||
|
for (var property : FightProperty.values()) {
|
||||||
|
var name = property.name();
|
||||||
|
var value = owner.getFightProperty(property);
|
||||||
|
properties.put(name, value);
|
||||||
|
}
|
||||||
|
// Add ability properties.
|
||||||
|
properties.putAll(ability.getAbilitySpecials());
|
||||||
|
|
||||||
var amountByCasterMaxHPRatio = action.amountByCasterMaxHPRatio.get(ability);
|
// Calculate ratios from properties.
|
||||||
var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(ability);
|
var amountByCasterMaxHPRatio = action.amountByCasterMaxHPRatio.get(properties, 0);
|
||||||
var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(ability);
|
var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(properties, 0);
|
||||||
var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(ability);
|
var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(properties, 0);
|
||||||
var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(ability);
|
var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(properties, 0);
|
||||||
|
var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(properties, 0);
|
||||||
|
|
||||||
Grasscutter.getLogger().trace("amountByCasterMaxHPRatio: " + amountByCasterMaxHPRatio);
|
var amountToRegenerate = action.amount.get(properties, 0);
|
||||||
Grasscutter.getLogger().trace("amountByCasterAttackRatio: " + amountByCasterAttackRatio);
|
|
||||||
Grasscutter.getLogger().trace("amountByCasterCurrentHPRatio: " + amountByCasterCurrentHPRatio);
|
|
||||||
Grasscutter.getLogger().trace("amountByTargetCurrentHPRatio: " + amountByTargetCurrentHPRatio);
|
|
||||||
Grasscutter.getLogger().trace("amountByTargetMaxHPRatio: " + amountByTargetMaxHPRatio);
|
|
||||||
|
|
||||||
var amountToRegenerate = action.amount.get(ability);
|
|
||||||
Grasscutter.getLogger().trace("Base amount: " + amountToRegenerate);
|
|
||||||
|
|
||||||
amountToRegenerate +=
|
amountToRegenerate +=
|
||||||
amountByCasterMaxHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
amountByCasterMaxHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||||
@ -57,25 +59,17 @@ public final class ActionHealHP extends AbilityActionHandler {
|
|||||||
amountToRegenerate +=
|
amountToRegenerate +=
|
||||||
amountByCasterCurrentHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
amountByCasterCurrentHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||||
|
|
||||||
Grasscutter.getLogger().trace("amountToRegenerate: " + amountToRegenerate);
|
|
||||||
|
|
||||||
var abilityRatio = 1.0f;
|
var abilityRatio = 1.0f;
|
||||||
Grasscutter.getLogger().trace("Base abilityRatio: " + abilityRatio);
|
|
||||||
|
|
||||||
if (!action.ignoreAbilityProperty)
|
if (!action.ignoreAbilityProperty)
|
||||||
abilityRatio +=
|
abilityRatio +=
|
||||||
target.getFightProperty(FightProperty.FIGHT_PROP_HEAL_ADD)
|
target.getFightProperty(FightProperty.FIGHT_PROP_HEAL_ADD)
|
||||||
+ target.getFightProperty(FightProperty.FIGHT_PROP_HEALED_ADD);
|
+ target.getFightProperty(FightProperty.FIGHT_PROP_HEALED_ADD);
|
||||||
|
|
||||||
Grasscutter.getLogger().trace("abilityRatio: " + abilityRatio);
|
|
||||||
|
|
||||||
Grasscutter.getLogger().trace("Sub-regenerate amount: " + amountToRegenerate);
|
|
||||||
amountToRegenerate +=
|
amountToRegenerate +=
|
||||||
amountByTargetCurrentHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
amountByTargetCurrentHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||||
amountToRegenerate +=
|
amountToRegenerate +=
|
||||||
amountByTargetMaxHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
amountByTargetMaxHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||||
|
|
||||||
Grasscutter.getLogger().trace("Healing {} without ratios", amountToRegenerate);
|
|
||||||
target.heal(
|
target.heal(
|
||||||
amountToRegenerate * abilityRatio * action.healRatio.get(ability, 1f),
|
amountToRegenerate * abilityRatio * action.healRatio.get(ability, 1f),
|
||||||
action.muteHealEffect);
|
action.muteHealEffect);
|
||||||
|
@ -8,10 +8,9 @@ import emu.grasscutter.game.player.*;
|
|||||||
import emu.grasscutter.game.props.*;
|
import emu.grasscutter.game.props.*;
|
||||||
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
|
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
|
||||||
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
|
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -80,7 +80,7 @@ public class TrialAvatarActivityHandler extends ActivityHandler {
|
|||||||
if (!player
|
if (!player
|
||||||
.getServer()
|
.getServer()
|
||||||
.getDungeonSystem()
|
.getDungeonSystem()
|
||||||
.enterDungeon(player, enterPointId, getTrialActivityDungeonId(trialAvatarIndexId)))
|
.enterDungeon(player, enterPointId, getTrialActivityDungeonId(trialAvatarIndexId), true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
setSelectedTrialAvatarIndex(trialAvatarIndexId);
|
setSelectedTrialAvatarIndex(trialAvatarIndexId);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.game.avatar;
|
package emu.grasscutter.game.avatar;
|
||||||
|
|
||||||
|
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
||||||
|
|
||||||
import dev.morphia.annotations.*;
|
import dev.morphia.annotations.*;
|
||||||
import emu.grasscutter.GameConstants;
|
import emu.grasscutter.GameConstants;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
@ -30,14 +32,11 @@ import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.helpers.ProtoHelper;
|
import emu.grasscutter.utils.helpers.ProtoHelper;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.*;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
|
|
||||||
import javax.annotation.*;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.*;
|
||||||
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
import lombok.*;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Entity(value = "avatars", useDiscriminator = false)
|
@Entity(value = "avatars", useDiscriminator = false)
|
||||||
public class Avatar {
|
public class Avatar {
|
||||||
|
@ -38,6 +38,7 @@ public final class DungeonManager {
|
|||||||
private boolean ended = false;
|
private boolean ended = false;
|
||||||
private int newestWayPoint = 0;
|
private int newestWayPoint = 0;
|
||||||
@Getter private int startSceneTime = 0;
|
@Getter private int startSceneTime = 0;
|
||||||
|
@Setter @Getter private boolean towerDungeon = false;
|
||||||
|
|
||||||
DungeonTrialTeam trialTeam = null;
|
DungeonTrialTeam trialTeam = null;
|
||||||
|
|
||||||
@ -282,6 +283,16 @@ public final class DungeonManager {
|
|||||||
|
|
||||||
// Call PlayerFinishDungeonEvent.
|
// Call PlayerFinishDungeonEvent.
|
||||||
new PlayerFinishDungeonEvent(this.getScene().getPlayers(), this.getScene(), this).call();
|
new PlayerFinishDungeonEvent(this.getScene().getPlayers(), this.getScene(), this).call();
|
||||||
|
|
||||||
|
// jump players to next dungeon if available
|
||||||
|
if (this.dungeonData.getPassJumpDungeon() != 0) {
|
||||||
|
for (var player : this.getScene().getPlayers()) {
|
||||||
|
player
|
||||||
|
.getServer()
|
||||||
|
.getDungeonSystem()
|
||||||
|
.enterDungeon(player, 0, this.dungeonData.getPassJumpDungeon(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void quitDungeon() {
|
public void quitDungeon() {
|
||||||
@ -313,15 +324,31 @@ public final class DungeonManager {
|
|||||||
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
|
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
scene
|
var future =
|
||||||
.getScriptManager()
|
scene
|
||||||
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
|
.getScriptManager()
|
||||||
|
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
|
||||||
|
// Note: There is a possible race condition with calling
|
||||||
|
// EVENT_DUNGEON_SETTLE here asynchronously:
|
||||||
|
// 1. EVENT_DUNGEON_SETTLE triggers some Lua-side logic,
|
||||||
|
// which may happen after 2 (below) finishes.
|
||||||
|
// 2. Some DungeonSettleListener could be comparing some
|
||||||
|
// Lua variable before its setting in 1 (above) finishes.
|
||||||
|
// For safety, ensure all events have finished before returning.
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
|
public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
|
||||||
if (scene.getDungeonSettleListeners() != null) {
|
if (scene.getDungeonSettleListeners() != null) {
|
||||||
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
|
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
|
||||||
}
|
}
|
||||||
|
if (isTowerDungeon()) {
|
||||||
|
scene.getPlayers().get(0).getTowerManager().onEnd();
|
||||||
|
}
|
||||||
ended = true;
|
ended = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ public final class DungeonSystem extends BaseGameSystem {
|
|||||||
return handler.execute(condition, params);
|
return handler.execute(condition, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
|
public boolean enterDungeon(Player player, int pointId, int dungeonId, boolean savePrevious) {
|
||||||
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
@ -103,7 +103,7 @@ public final class DungeonSystem extends BaseGameSystem {
|
|||||||
|
|
||||||
var sceneId = data.getSceneId();
|
var sceneId = data.getSceneId();
|
||||||
var scene = player.getScene();
|
var scene = player.getScene();
|
||||||
scene.setPrevScene(sceneId);
|
if (savePrevious) scene.setPrevScene(scene.getId());
|
||||||
|
|
||||||
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
||||||
scene = player.getScene();
|
scene = player.getScene();
|
||||||
@ -111,7 +111,7 @@ public final class DungeonSystem extends BaseGameSystem {
|
|||||||
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
|
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.setPrevScenePoint(pointId);
|
if (savePrevious) scene.setPrevScenePoint(pointId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,11 @@ public final class DungeonSystem extends BaseGameSystem {
|
|||||||
dungeonId);
|
dungeonId);
|
||||||
|
|
||||||
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
|
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
|
||||||
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
|
var scene = player.getScene();
|
||||||
|
var dungeonManager = new DungeonManager(scene, data);
|
||||||
|
dungeonManager.setTowerDungeon(true);
|
||||||
|
scene.setDungeonManager(dungeonManager);
|
||||||
|
dungeonSettleListeners.forEach(scene::addDungeonSettleObserver);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -164,11 +168,40 @@ public final class DungeonSystem extends BaseGameSystem {
|
|||||||
dungeonManager.unsetTrialTeam(player);
|
dungeonManager.unsetTrialTeam(player);
|
||||||
}
|
}
|
||||||
// clean temp team if it has
|
// clean temp team if it has
|
||||||
player.getTeamManager().cleanTemporaryTeam();
|
if (!player.getTeamManager().cleanTemporaryTeam()) {
|
||||||
|
// no temp team. Will use real current team, but check
|
||||||
|
// for any dead avatar to prevent switching into them.
|
||||||
|
player.getTeamManager().checkCurrentAvatarIsAlive(null);
|
||||||
|
}
|
||||||
player.getTowerManager().clearEntry();
|
player.getTowerManager().clearEntry();
|
||||||
|
dungeonManager.setTowerDungeon(false);
|
||||||
|
|
||||||
// Transfer player back to world
|
// Transfer player back to world after a small delay.
|
||||||
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
|
// This wait is important for avoiding double teleports,
|
||||||
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
|
// which specifically happen when player quits a dungeon
|
||||||
|
// by teleporting to map waypoints.
|
||||||
|
// From testing, 200ms seem reasonable.
|
||||||
|
player.getWorld().queueTransferPlayerToScene(player, prevScene, prevPos, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartDungeon(Player player) {
|
||||||
|
var scene = player.getScene();
|
||||||
|
var dungeonManager = scene.getDungeonManager();
|
||||||
|
var dungeonData = dungeonManager.getDungeonData();
|
||||||
|
var sceneId = dungeonData.getSceneId();
|
||||||
|
|
||||||
|
// Forward over previous scene and scene point
|
||||||
|
var prevScene = scene.getPrevScene();
|
||||||
|
var pointId = scene.getPrevScenePoint();
|
||||||
|
|
||||||
|
// Destroy then create scene again to reinitialize script state
|
||||||
|
scene.getPlayers().forEach(scene::removePlayer);
|
||||||
|
if (player.getWorld().transferPlayerToScene(player, sceneId, dungeonData)) {
|
||||||
|
scene = player.getScene();
|
||||||
|
scene.setPrevScene(prevScene);
|
||||||
|
scene.setPrevScenePoint(pointId);
|
||||||
|
scene.setDungeonManager(new DungeonManager(scene, dungeonData));
|
||||||
|
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
|
public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
|
||||||
var scene = dungeonManager.getScene();
|
var scene = dungeonManager.getScene();
|
||||||
|
|
||||||
var dungeonData = dungeonManager.getDungeonData();
|
var dungeonData = dungeonManager.getDungeonData();
|
||||||
if (scene.getLoadedGroups().stream()
|
if (scene.getLoadedGroups().stream()
|
||||||
.anyMatch(
|
.anyMatch(
|
||||||
@ -22,17 +23,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
||||||
|
var stars = towerManager.getCurLevelStars();
|
||||||
|
|
||||||
towerManager.notifyCurLevelRecordChangeWhenDone(3);
|
towerManager.notifyCurLevelRecordChangeWhenDone(stars);
|
||||||
scene.broadcastPacket(
|
scene.broadcastPacket(
|
||||||
new PacketTowerFloorRecordChangeNotify(
|
new PacketTowerFloorRecordChangeNotify(
|
||||||
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor()));
|
towerManager.getCurrentFloorId(), stars, towerManager.canEnterScheduleFloor()));
|
||||||
|
|
||||||
var challenge = scene.getChallenge();
|
var challenge = scene.getChallenge();
|
||||||
var dungeonStats =
|
var dungeonStats =
|
||||||
new DungeonEndStats(
|
new DungeonEndStats(
|
||||||
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
|
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
|
||||||
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge);
|
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge, stars);
|
||||||
|
|
||||||
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
|
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,16 @@ public class WorldChallenge {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.progress = true;
|
this.progress = true;
|
||||||
this.startedAt = System.currentTimeMillis();
|
this.startedAt = getScene().getSceneTimeSeconds();
|
||||||
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
|
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
|
||||||
challengeTriggers.forEach(t -> t.onBegin(this));
|
challengeTriggers.forEach(t -> t.onBegin(this));
|
||||||
|
|
||||||
|
var player = scene.getPlayers().get(0);
|
||||||
|
var dungeonManager = scene.getDungeonManager();
|
||||||
|
var towerManager = player.getTowerManager();
|
||||||
|
if (dungeonManager != null && dungeonManager.isTowerDungeon() && towerManager != null) {
|
||||||
|
towerManager.onBegin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void done() {
|
public void done() {
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
package emu.grasscutter.game.dungeons.challenge.trigger;
|
package emu.grasscutter.game.dungeons.challenge.trigger;
|
||||||
|
|
||||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
||||||
|
|
||||||
public class InTimeTrigger extends ChallengeTrigger {
|
public class InTimeTrigger extends ChallengeTrigger {
|
||||||
|
@Override
|
||||||
|
public void onBegin(WorldChallenge challenge) {
|
||||||
|
// Show time remaining UI
|
||||||
|
var scene = challenge.getScene();
|
||||||
|
scene.broadcastPacket(
|
||||||
|
new PacketChallengeDataNotify(
|
||||||
|
challenge,
|
||||||
|
2,
|
||||||
|
// Compensate for time passed so far in scene.
|
||||||
|
challenge.getTimeLimit() + scene.getSceneTimeSeconds()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckTimeout(WorldChallenge challenge) {
|
public void onCheckTimeout(WorldChallenge challenge) {
|
||||||
|
// In Tower challenges, time can run out without
|
||||||
|
// causing the challenge to fail. (Player just
|
||||||
|
// gets 0 stars when they ultimately finish.)
|
||||||
|
var dungeonManager = challenge.getScene().getDungeonManager();
|
||||||
|
if (dungeonManager != null && dungeonManager.isTowerDungeon()) return;
|
||||||
|
|
||||||
var current = challenge.getScene().getSceneTimeSeconds();
|
var current = challenge.getScene().getSceneTimeSeconds();
|
||||||
if (current - challenge.getStartedAt() > challenge.getTimeLimit()) {
|
if (current - challenge.getStartedAt() > challenge.getTimeLimit()) {
|
||||||
challenge.fail();
|
challenge.fail();
|
||||||
|
@ -13,17 +13,20 @@ public class TowerResult extends BaseDungeonResult {
|
|||||||
boolean canJump;
|
boolean canJump;
|
||||||
boolean hasNextLevel;
|
boolean hasNextLevel;
|
||||||
int nextFloorId;
|
int nextFloorId;
|
||||||
|
int currentStars;
|
||||||
|
|
||||||
public TowerResult(
|
public TowerResult(
|
||||||
DungeonData dungeonData,
|
DungeonData dungeonData,
|
||||||
DungeonEndStats dungeonStats,
|
DungeonEndStats dungeonStats,
|
||||||
TowerManager towerManager,
|
TowerManager towerManager,
|
||||||
WorldChallenge challenge) {
|
WorldChallenge challenge,
|
||||||
|
int currentStars) {
|
||||||
super(dungeonData, dungeonStats);
|
super(dungeonData, dungeonStats);
|
||||||
this.challenge = challenge;
|
this.challenge = challenge;
|
||||||
this.canJump = towerManager.hasNextFloor();
|
this.canJump = towerManager.hasNextFloor();
|
||||||
this.hasNextLevel = towerManager.hasNextLevel();
|
this.hasNextLevel = towerManager.hasNextLevel();
|
||||||
this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId();
|
this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId();
|
||||||
|
this.currentStars = currentStars;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,14 +43,16 @@ public class TowerResult extends BaseDungeonResult {
|
|||||||
TowerLevelEndNotify.newBuilder()
|
TowerLevelEndNotify.newBuilder()
|
||||||
.setIsSuccess(challenge.isSuccess())
|
.setIsSuccess(challenge.isSuccess())
|
||||||
.setContinueState(continueStatus)
|
.setContinueState(continueStatus)
|
||||||
.addFinishedStarCondList(1)
|
|
||||||
.addFinishedStarCondList(2)
|
|
||||||
.addFinishedStarCondList(3)
|
|
||||||
.addRewardItemList(
|
.addRewardItemList(
|
||||||
ItemParamOuterClass.ItemParam.newBuilder().setItemId(201).setCount(1000).build());
|
ItemParamOuterClass.ItemParam.newBuilder().setItemId(201).setCount(1000));
|
||||||
|
|
||||||
|
for (int i = 1; i <= currentStars; i++) {
|
||||||
|
towerLevelEndNotify.addFinishedStarCondList(i);
|
||||||
|
}
|
||||||
|
|
||||||
if (nextFloorId > 0 && canJump) {
|
if (nextFloorId > 0 && canJump) {
|
||||||
towerLevelEndNotify.setNextFloorId(nextFloorId);
|
towerLevelEndNotify.setNextFloorId(nextFloorId);
|
||||||
}
|
}
|
||||||
builder.setTowerLevelEndNotify(towerLevelEndNotify);
|
builder.setTowerLevelEndNotify(towerLevelEndNotify.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package emu.grasscutter.game.dungeons.enums;
|
||||||
|
|
||||||
|
public enum DungeonEntryCondCombType {
|
||||||
|
DUNGEON_ENTRY_COND_COMB_NONE,
|
||||||
|
DUNGEON_ENTRY_COND_COMB_LOGIC_OR,
|
||||||
|
DUNGEON_ENTRY_COND_COMB_LOGIC_AND
|
||||||
|
}
|
@ -67,6 +67,11 @@ public class EntityAvatar extends GameEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.initAbilities();
|
this.initAbilities();
|
||||||
|
|
||||||
|
// New EntityAvatar instances are created on every scene transition.
|
||||||
|
// Ensure that isDead is properly carried over between scenes.
|
||||||
|
// Otherwise avatars could have 0 HP but not considered dead.
|
||||||
|
this.checkIfDead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -88,11 +93,6 @@ public class EntityAvatar extends GameEntity {
|
|||||||
return getPlayer().getRotation();
|
return getPlayer().getRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Int2FloatMap getFightProperties() {
|
public Int2FloatMap getFightProperties() {
|
||||||
return getAvatar().getFightProperties();
|
return getAvatar().getFightProperties();
|
||||||
@ -137,13 +137,19 @@ public class EntityAvatar extends GameEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float heal(float amount, boolean mute) {
|
public float heal(float amount, boolean mute) {
|
||||||
// Do not heal character if they are dead
|
// Do not heal character if they are dead.
|
||||||
if (!this.isAlive()) {
|
var currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||||
|
if (currentHp <= 0) {
|
||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float healed = super.heal(amount, mute);
|
// Check if the character hasn't been marked as dead.
|
||||||
|
if (currentHp > 0 && this.isDead()) {
|
||||||
|
this.setDead(false);
|
||||||
|
mute = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float healed = super.heal(amount, mute);
|
||||||
if (healed > 0f) {
|
if (healed > 0f) {
|
||||||
getScene()
|
getScene()
|
||||||
.broadcastPacket(
|
.broadcastPacket(
|
||||||
|
@ -256,6 +256,9 @@ public class EntityGadget extends EntityBaseGadget {
|
|||||||
var route = this.getScene().getSceneRouteById(configRoute.getRouteId());
|
var route = this.getScene().getSceneRouteById(configRoute.getRouteId());
|
||||||
if (route != null) {
|
if (route != null) {
|
||||||
var points = route.getPoints();
|
var points = route.getPoints();
|
||||||
|
if (configRoute.getStartIndex() == points.length - 1) {
|
||||||
|
configRoute.setStartIndex(0);
|
||||||
|
}
|
||||||
val currIndex = configRoute.getStartIndex();
|
val currIndex = configRoute.getStartIndex();
|
||||||
|
|
||||||
Position prevpos;
|
Position prevpos;
|
||||||
@ -301,6 +304,9 @@ public class EntityGadget extends EntityBaseGadget {
|
|||||||
}
|
}
|
||||||
configRoute.setStartIndex(I);
|
configRoute.setStartIndex(I);
|
||||||
this.position.set(points[I].getPos());
|
this.position.set(points[I].getPos());
|
||||||
|
if (I == points.length - 1) {
|
||||||
|
configRoute.setStarted(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(int) time));
|
(int) time));
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import emu.grasscutter.game.world.Scene;
|
|||||||
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||||
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
|
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
|
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
||||||
@ -15,10 +16,10 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
|||||||
private final Position rebornPos;
|
private final Position rebornPos;
|
||||||
@Getter private final int rebirth;
|
@Getter private final int rebirth;
|
||||||
@Getter private final int rebirthCD;
|
@Getter private final int rebirthCD;
|
||||||
private boolean disappeared;
|
private final AtomicBoolean disappeared = new AtomicBoolean();
|
||||||
|
|
||||||
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos) {
|
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos, Position rot) {
|
||||||
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, 1);
|
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, rot, 1);
|
||||||
|
|
||||||
this.rebornPos = pos.clone();
|
this.rebornPos = pos.clone();
|
||||||
this.rebirth = data.getIsRebirth();
|
this.rebirth = data.getIsRebirth();
|
||||||
@ -60,13 +61,13 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
|||||||
new PacketSceneEntityDisappearNotify(
|
new PacketSceneEntityDisappearNotify(
|
||||||
this, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
|
this, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
|
||||||
this.rebornCDTickCount = this.getRebornCD();
|
this.rebornCDTickCount = this.getRebornCD();
|
||||||
this.disappeared = true;
|
this.disappeared.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reborn() {
|
public void reborn() {
|
||||||
if (this.disappeared) {
|
if (this.disappeared.get()) {
|
||||||
this.disappeared = false;
|
this.disappeared.set(false);
|
||||||
this.getPosition().set(this.getRebornPos());
|
this.getPosition().set(this.getRebornPos());
|
||||||
this.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(this));
|
this.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(this));
|
||||||
}
|
}
|
||||||
@ -74,6 +75,6 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInCD() {
|
public boolean isInCD() {
|
||||||
return this.disappeared;
|
return this.disappeared.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package emu.grasscutter.game.entity;
|
package emu.grasscutter.game.entity;
|
||||||
|
|
||||||
|
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE;
|
||||||
|
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.binout.config.ConfigEntityMonster;
|
import emu.grasscutter.data.binout.config.ConfigEntityMonster;
|
||||||
import emu.grasscutter.data.common.PropGrowCurve;
|
import emu.grasscutter.data.common.PropGrowCurve;
|
||||||
@ -28,12 +30,9 @@ import emu.grasscutter.scripts.data.*;
|
|||||||
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
||||||
import emu.grasscutter.utils.helpers.ProtoHelper;
|
import emu.grasscutter.utils.helpers.ProtoHelper;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE;
|
import lombok.*;
|
||||||
|
|
||||||
public class EntityMonster extends GameEntity {
|
public class EntityMonster extends GameEntity {
|
||||||
@Getter(onMethod_ = @Override)
|
@Getter(onMethod_ = @Override)
|
||||||
@ -41,8 +40,10 @@ public class EntityMonster extends GameEntity {
|
|||||||
|
|
||||||
@Getter(onMethod_ = @Override)
|
@Getter(onMethod_ = @Override)
|
||||||
private final Position position;
|
private final Position position;
|
||||||
|
|
||||||
@Getter(onMethod_ = @Override)
|
@Getter(onMethod_ = @Override)
|
||||||
private final Position rotation;
|
private final Position rotation;
|
||||||
|
|
||||||
@Getter private final MonsterData monsterData;
|
@Getter private final MonsterData monsterData;
|
||||||
@Getter private final ConfigEntityMonster configEntityMonster;
|
@Getter private final ConfigEntityMonster configEntityMonster;
|
||||||
@Getter private final Position bornPos;
|
@Getter private final Position bornPos;
|
||||||
@ -54,21 +55,23 @@ public class EntityMonster extends GameEntity {
|
|||||||
@Getter private List<Player> playerOnBattle;
|
@Getter private List<Player> playerOnBattle;
|
||||||
@Nullable @Getter @Setter private SceneMonster metaMonster;
|
@Nullable @Getter @Setter private SceneMonster metaMonster;
|
||||||
|
|
||||||
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
|
public EntityMonster(
|
||||||
|
Scene scene, MonsterData monsterData, Position pos, Position rot, int level) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
|
||||||
this.id = this.getWorld().getNextEntityId(EntityIdType.MONSTER);
|
this.id = this.getWorld().getNextEntityId(EntityIdType.MONSTER);
|
||||||
this.monsterData = monsterData;
|
this.monsterData = monsterData;
|
||||||
this.fightProperties = new Int2FloatOpenHashMap();
|
this.fightProperties = new Int2FloatOpenHashMap();
|
||||||
this.position = new Position(pos);
|
this.position = new Position(pos);
|
||||||
this.rotation = new Position();
|
this.rotation = new Position(rot);
|
||||||
this.bornPos = this.getPosition().clone();
|
this.bornPos = this.getPosition().clone();
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.playerOnBattle = new ArrayList<>();
|
this.playerOnBattle = new ArrayList<>();
|
||||||
|
|
||||||
if (GameData.getMonsterMappingMap().containsKey(this.getMonsterId())) {
|
if (GameData.getMonsterMappingMap().containsKey(this.getMonsterId())) {
|
||||||
this.configEntityMonster = GameData.getMonsterConfigData().get(
|
this.configEntityMonster =
|
||||||
GameData.getMonsterMappingMap().get(this.getMonsterId()).getMonsterJson());
|
GameData.getMonsterConfigData()
|
||||||
|
.get(GameData.getMonsterMappingMap().get(this.getMonsterId()).getMonsterJson());
|
||||||
} else {
|
} else {
|
||||||
this.configEntityMonster = null;
|
this.configEntityMonster = null;
|
||||||
}
|
}
|
||||||
@ -77,7 +80,7 @@ public class EntityMonster extends GameEntity {
|
|||||||
if (getMonsterWeaponId() > 0) {
|
if (getMonsterWeaponId() > 0) {
|
||||||
this.weaponEntity = new EntityWeapon(scene, getMonsterWeaponId());
|
this.weaponEntity = new EntityWeapon(scene, getMonsterWeaponId());
|
||||||
scene.getWeaponEntities().put(this.weaponEntity.getId(), this.weaponEntity);
|
scene.getWeaponEntities().put(this.weaponEntity.getId(), this.weaponEntity);
|
||||||
//this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
|
// this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recalcStats();
|
this.recalcStats();
|
||||||
@ -87,18 +90,15 @@ public class EntityMonster extends GameEntity {
|
|||||||
private void addConfigAbility(String name) {
|
private void addConfigAbility(String name) {
|
||||||
var data = GameData.getAbilityData(name);
|
var data = GameData.getAbilityData(name);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
this.getWorld().getHost()
|
this.getWorld().getHost().getAbilityManager().addAbilityToEntity(this, data);
|
||||||
.getAbilityManager()
|
|
||||||
.addAbilityToEntity(this, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initAbilities() {
|
public void initAbilities() {
|
||||||
// Affix abilities
|
// Affix abilities
|
||||||
var optionalGroup = this.getScene().getLoadedGroups().stream()
|
var optionalGroup =
|
||||||
.filter(g -> g.id == this.getGroupId())
|
this.getScene().getLoadedGroups().stream().filter(g -> g.id == this.getGroupId()).findAny();
|
||||||
.findAny();
|
|
||||||
List<Integer> affixes = null;
|
List<Integer> affixes = null;
|
||||||
if (optionalGroup.isPresent()) {
|
if (optionalGroup.isPresent()) {
|
||||||
var group = optionalGroup.get();
|
var group = optionalGroup.get();
|
||||||
@ -118,7 +118,7 @@ public class EntityMonster extends GameEntity {
|
|||||||
var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue());
|
var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue());
|
||||||
if (!affix.isPreAdd()) continue;
|
if (!affix.isPreAdd()) continue;
|
||||||
|
|
||||||
//Add the ability
|
// Add the ability
|
||||||
for (var name : affix.getAbilityName()) {
|
for (var name : affix.getAbilityName()) {
|
||||||
this.addConfigAbility(name);
|
this.addConfigAbility(name);
|
||||||
}
|
}
|
||||||
@ -126,14 +126,12 @@ public class EntityMonster extends GameEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Research if any monster is non humanoid
|
// TODO: Research if any monster is non humanoid
|
||||||
for(var ability : GameData.getConfigGlobalCombat()
|
for (var ability :
|
||||||
.getDefaultAbilities()
|
GameData.getConfigGlobalCombat().getDefaultAbilities().getNonHumanoidMoveAbilities()) {
|
||||||
.getNonHumanoidMoveAbilities()) {
|
|
||||||
this.addConfigAbility(ability);
|
this.addConfigAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.configEntityMonster != null &&
|
if (this.configEntityMonster != null && this.configEntityMonster.getAbilities() != null) {
|
||||||
this.configEntityMonster.getAbilities() != null) {
|
|
||||||
for (var configAbilityData : this.configEntityMonster.getAbilities()) {
|
for (var configAbilityData : this.configEntityMonster.getAbilities()) {
|
||||||
this.addConfigAbility(configAbilityData.abilityName);
|
this.addConfigAbility(configAbilityData.abilityName);
|
||||||
}
|
}
|
||||||
@ -143,9 +141,8 @@ public class EntityMonster extends GameEntity {
|
|||||||
var group = optionalGroup.get();
|
var group = optionalGroup.get();
|
||||||
var monster = group.monsters.get(getConfigId());
|
var monster = group.monsters.get(getConfigId());
|
||||||
if (monster != null && monster.isElite) {
|
if (monster != null && monster.isElite) {
|
||||||
this.addConfigAbility(GameData.getConfigGlobalCombat()
|
this.addConfigAbility(
|
||||||
.getDefaultAbilities()
|
GameData.getConfigGlobalCombat().getDefaultAbilities().getMonterEliteAbilityName());
|
||||||
.getMonterEliteAbilityName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +151,8 @@ public class EntityMonster extends GameEntity {
|
|||||||
var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue());
|
var affix = GameData.getMonsterAffixDataMap().get(affixId.intValue());
|
||||||
if (affix.isPreAdd()) continue;
|
if (affix.isPreAdd()) continue;
|
||||||
|
|
||||||
//Add the ability
|
// Add the ability
|
||||||
for(var name : affix.getAbilityName()) {
|
for (var name : affix.getAbilityName()) {
|
||||||
this.addConfigAbility(name);
|
this.addConfigAbility(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +160,7 @@ public class EntityMonster extends GameEntity {
|
|||||||
|
|
||||||
var levelEntityConfig = getScene().getSceneData().getLevelEntityConfig();
|
var levelEntityConfig = getScene().getSceneData().getLevelEntityConfig();
|
||||||
var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig);
|
var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig);
|
||||||
if (config == null){
|
if (config == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +191,8 @@ public class EntityMonster extends GameEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInteract(Player player, GadgetInteractReq interactReq) {
|
public void onInteract(Player player, GadgetInteractReq interactReq) {
|
||||||
EnvAnimalGatherConfigData gatherData = GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
|
EnvAnimalGatherConfigData gatherData =
|
||||||
|
GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
|
||||||
|
|
||||||
if (gatherData == null) {
|
if (gatherData == null) {
|
||||||
return;
|
return;
|
||||||
@ -208,7 +206,11 @@ public class EntityMonster extends GameEntity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
// Lua event
|
// Lua event
|
||||||
getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_LIVE, this.getConfigId()));
|
getScene()
|
||||||
|
.getScriptManager()
|
||||||
|
.callEvent(
|
||||||
|
new ScriptArgs(
|
||||||
|
this.getGroupId(), EventType.EVENT_ANY_MONSTER_LIVE, this.getConfigId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -231,10 +233,17 @@ public class EntityMonster extends GameEntity {
|
|||||||
@Override
|
@Override
|
||||||
public void runLuaCallbacks(EntityDamageEvent event) {
|
public void runLuaCallbacks(EntityDamageEvent event) {
|
||||||
super.runLuaCallbacks(event);
|
super.runLuaCallbacks(event);
|
||||||
getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EVENT_SPECIFIC_MONSTER_HP_CHANGE, getConfigId(), monsterData.getId())
|
getScene()
|
||||||
.setSourceEntityId(getId())
|
.getScriptManager()
|
||||||
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
|
.callEvent(
|
||||||
.setEventSource(getConfigId()));
|
new ScriptArgs(
|
||||||
|
this.getGroupId(),
|
||||||
|
EVENT_SPECIFIC_MONSTER_HP_CHANGE,
|
||||||
|
getConfigId(),
|
||||||
|
monsterData.getId())
|
||||||
|
.setSourceEntityId(getId())
|
||||||
|
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
|
||||||
|
.setEventSource(getConfigId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -250,29 +259,63 @@ public class EntityMonster extends GameEntity {
|
|||||||
challenge.ifPresent(c -> c.onMonsterDeath(this));
|
challenge.ifPresent(c -> c.onMonsterDeath(this));
|
||||||
|
|
||||||
if (scriptManager.isInit() && this.getGroupId() > 0) {
|
if (scriptManager.isInit() && this.getGroupId() > 0) {
|
||||||
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService()).ifPresent(s -> s.onMonsterDead(this));
|
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService())
|
||||||
|
.ifPresent(s -> s.onMonsterDead(this));
|
||||||
|
|
||||||
// prevent spawn monster after success
|
// Ensure each EVENT_ANY_MONSTER_DIE runs to completion.
|
||||||
/*if (challenge.map(c -> c.inProgress()).orElse(true)) {
|
// Multiple such events firing at the same time may cause
|
||||||
scriptManager.callEvent(new ScriptArgs(EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()).setGroupId(this.getGroupId()));
|
// the same lua trigger to fire multiple times, when it
|
||||||
} else if (getScene().getChallenge() == null) {
|
// should happen only once.
|
||||||
}*/
|
var future =
|
||||||
scriptManager.callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()));
|
scriptManager.callEvent(
|
||||||
|
new ScriptArgs(
|
||||||
|
this.getGroupId(), EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()));
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Battle Pass trigger
|
// Battle Pass trigger
|
||||||
scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
|
scene
|
||||||
|
.getPlayers()
|
||||||
|
.forEach(
|
||||||
|
p ->
|
||||||
|
p.getBattlePassManager()
|
||||||
|
.triggerMission(
|
||||||
|
WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
|
||||||
|
|
||||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_MONSTER_DIE, this.getMonsterId()));
|
scene
|
||||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_KILL_MONSTER, this.getMonsterId()));
|
.getPlayers()
|
||||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_CLEAR_GROUP_MONSTER, this.getGroupId()));
|
.forEach(
|
||||||
|
p ->
|
||||||
|
p.getQuestManager()
|
||||||
|
.queueEvent(QuestContent.QUEST_CONTENT_MONSTER_DIE, this.getMonsterId()));
|
||||||
|
scene
|
||||||
|
.getPlayers()
|
||||||
|
.forEach(
|
||||||
|
p ->
|
||||||
|
p.getQuestManager()
|
||||||
|
.queueEvent(QuestContent.QUEST_CONTENT_KILL_MONSTER, this.getMonsterId()));
|
||||||
|
scene
|
||||||
|
.getPlayers()
|
||||||
|
.forEach(
|
||||||
|
p ->
|
||||||
|
p.getQuestManager()
|
||||||
|
.queueEvent(QuestContent.QUEST_CONTENT_CLEAR_GROUP_MONSTER, this.getGroupId()));
|
||||||
|
|
||||||
SceneGroupInstance groupInstance = scene.getScriptManager().getGroupInstanceById(this.getGroupId());
|
SceneGroupInstance groupInstance =
|
||||||
if(groupInstance != null && metaMonster != null)
|
scene.getScriptManager().getGroupInstanceById(this.getGroupId());
|
||||||
|
if (groupInstance != null && metaMonster != null)
|
||||||
groupInstance.getDeadEntities().add(metaMonster.config_id);
|
groupInstance.getDeadEntities().add(metaMonster.config_id);
|
||||||
|
|
||||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_GROUP_MONSTER, this.getGroupId());
|
scene.triggerDungeonEvent(
|
||||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_TYPE_MONSTER, this.getMonsterData().getType().getValue());
|
DungeonPassConditionType.DUNGEON_COND_KILL_GROUP_MONSTER, this.getGroupId());
|
||||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
scene.triggerDungeonEvent(
|
||||||
|
DungeonPassConditionType.DUNGEON_COND_KILL_TYPE_MONSTER,
|
||||||
|
this.getMonsterData().getType().getValue());
|
||||||
|
scene.triggerDungeonEvent(
|
||||||
|
DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recalcStats() {
|
public void recalcStats() {
|
||||||
@ -280,86 +323,107 @@ public class EntityMonster extends GameEntity {
|
|||||||
MonsterData data = this.getMonsterData();
|
MonsterData data = this.getMonsterData();
|
||||||
|
|
||||||
// Get hp percent, set to 100% if none
|
// 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);
|
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
|
// Clear properties
|
||||||
this.getFightProperties().clear();
|
this.getFightProperties().clear();
|
||||||
|
|
||||||
// Base stats
|
// Base stats
|
||||||
MonsterData.definedFightProperties.forEach(prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
|
MonsterData.definedFightProperties.forEach(
|
||||||
|
prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
|
||||||
|
|
||||||
// Level curve
|
// Level curve
|
||||||
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
|
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
|
||||||
if (curve != null) {
|
if (curve != null) {
|
||||||
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
|
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
|
||||||
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
|
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
|
||||||
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
|
this.setFightProperty(
|
||||||
|
prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set % stats
|
// Set % stats
|
||||||
FightProperty.forEachCompoundProperty(c -> this.setFightProperty(c.getResult(),
|
FightProperty.forEachCompoundProperty(
|
||||||
this.getFightProperty(c.getFlat()) + (this.getFightProperty(c.getBase()) * (1f + this.getFightProperty(c.getPercent())))));
|
c ->
|
||||||
|
this.setFightProperty(
|
||||||
|
c.getResult(),
|
||||||
|
this.getFightProperty(c.getFlat())
|
||||||
|
+ (this.getFightProperty(c.getBase())
|
||||||
|
* (1f + this.getFightProperty(c.getPercent())))));
|
||||||
|
|
||||||
// Set current hp
|
// Set current hp
|
||||||
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
|
this.setFightProperty(
|
||||||
|
FightProperty.FIGHT_PROP_CUR_HP,
|
||||||
|
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SceneEntityInfo toProto() {
|
public SceneEntityInfo toProto() {
|
||||||
var data = this.getMonsterData();
|
var data = this.getMonsterData();
|
||||||
|
|
||||||
var authority = EntityAuthorityInfo.newBuilder()
|
var authority =
|
||||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
EntityAuthorityInfo.newBuilder()
|
||||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||||
.setAiInfo(SceneEntityAiInfo.newBuilder()
|
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||||
.setIsAiOpen(true)
|
.setAiInfo(
|
||||||
.setBornPos(this.getBornPos().toProto()))
|
SceneEntityAiInfo.newBuilder()
|
||||||
.setBornPos(this.getBornPos().toProto())
|
.setIsAiOpen(true)
|
||||||
.build();
|
.setBornPos(this.getBornPos().toProto()))
|
||||||
|
.setBornPos(this.getBornPos().toProto())
|
||||||
|
.build();
|
||||||
|
|
||||||
var entityInfo = SceneEntityInfo.newBuilder()
|
var entityInfo =
|
||||||
.setEntityId(this.getId())
|
SceneEntityInfo.newBuilder()
|
||||||
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
|
.setEntityId(this.getId())
|
||||||
.setMotionInfo(this.getMotionInfo())
|
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
|
||||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
.setMotionInfo(this.getMotionInfo())
|
||||||
.setEntityClientData(EntityClientData.newBuilder())
|
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||||
.setEntityAuthorityInfo(authority)
|
.setEntityClientData(EntityClientData.newBuilder())
|
||||||
.setLifeState(this.getLifeState().getValue());
|
.setEntityAuthorityInfo(authority)
|
||||||
|
.setLifeState(this.getLifeState().getValue());
|
||||||
|
|
||||||
this.addAllFightPropsToEntityInfo(entityInfo);
|
this.addAllFightPropsToEntityInfo(entityInfo);
|
||||||
|
|
||||||
entityInfo.addPropList(PropPair.newBuilder()
|
entityInfo.addPropList(
|
||||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
PropPair.newBuilder()
|
||||||
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()))
|
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||||
.build());
|
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()))
|
||||||
|
.build());
|
||||||
|
|
||||||
var monsterInfo = SceneMonsterInfo.newBuilder()
|
var monsterInfo =
|
||||||
.setMonsterId(getMonsterId())
|
SceneMonsterInfo.newBuilder()
|
||||||
.setGroupId(this.getGroupId())
|
.setMonsterId(getMonsterId())
|
||||||
.setConfigId(this.getConfigId())
|
.setGroupId(this.getGroupId())
|
||||||
.addAllAffixList(data.getAffix())
|
.setConfigId(this.getConfigId())
|
||||||
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
.addAllAffixList(data.getAffix())
|
||||||
.setPoseId(this.getPoseId())
|
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
||||||
.setBlockId(this.getScene().getId())
|
.setPoseId(this.getPoseId())
|
||||||
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
.setBlockId(this.getScene().getId())
|
||||||
|
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
||||||
|
|
||||||
if (this.metaMonster != null) {
|
if (this.metaMonster != null) {
|
||||||
if (this.metaMonster.special_name_id != 0) {
|
if (this.metaMonster.special_name_id != 0) {
|
||||||
monsterInfo.setTitleId(this.metaMonster.title_id)
|
monsterInfo
|
||||||
.setSpecialNameId(this.metaMonster.special_name_id);
|
.setTitleId(this.metaMonster.title_id)
|
||||||
|
.setSpecialNameId(this.metaMonster.special_name_id);
|
||||||
} else if (data.getDescribeData() != null) {
|
} else if (data.getDescribeData() != null) {
|
||||||
monsterInfo.setTitleId(data.getDescribeData().getTitleId())
|
monsterInfo
|
||||||
.setSpecialNameId(data.getSpecialNameId());
|
.setTitleId(data.getDescribeData().getTitleId())
|
||||||
|
.setSpecialNameId(data.getSpecialNameId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getMonsterWeaponId() > 0) {
|
if (this.getMonsterWeaponId() > 0) {
|
||||||
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
|
SceneWeaponInfo weaponInfo =
|
||||||
.setEntityId(this.getWeaponEntity() != null ? this.getWeaponEntity().getId() : 0)
|
SceneWeaponInfo.newBuilder()
|
||||||
.setGadgetId(this.getMonsterWeaponId())
|
.setEntityId(this.getWeaponEntity() != null ? this.getWeaponEntity().getId() : 0)
|
||||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
.setGadgetId(this.getMonsterWeaponId())
|
||||||
.build();
|
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||||
|
.build();
|
||||||
|
|
||||||
monsterInfo.addWeaponList(weaponInfo);
|
monsterInfo.addWeaponList(weaponInfo);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,10 @@ public abstract class GameEntity {
|
|||||||
|
|
||||||
@Getter @Setter private boolean lockHP;
|
@Getter @Setter private boolean lockHP;
|
||||||
|
|
||||||
|
@Setter(AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
private boolean isDead = false;
|
||||||
|
|
||||||
// Lua controller for specific actions
|
// Lua controller for specific actions
|
||||||
@Getter @Setter private EntityController entityController;
|
@Getter @Setter private EntityController entityController;
|
||||||
@Getter private ElementType lastAttackType = ElementType.None;
|
@Getter private ElementType lastAttackType = ElementType.None;
|
||||||
@ -63,7 +67,7 @@ public abstract class GameEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlive() {
|
public boolean isAlive() {
|
||||||
return true;
|
return !this.isDead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LifeState getLifeState() {
|
public LifeState getLifeState() {
|
||||||
@ -170,14 +174,7 @@ public abstract class GameEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lastAttackType = attackType;
|
this.lastAttackType = attackType;
|
||||||
|
this.checkIfDead();
|
||||||
// Check if dead
|
|
||||||
boolean isDead = false;
|
|
||||||
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
|
||||||
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
|
|
||||||
isDead = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.runLuaCallbacks(event);
|
this.runLuaCallbacks(event);
|
||||||
|
|
||||||
// Packets
|
// Packets
|
||||||
@ -186,11 +183,22 @@ public abstract class GameEntity {
|
|||||||
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
|
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
|
||||||
|
|
||||||
// Check if dead.
|
// Check if dead.
|
||||||
if (isDead) {
|
if (this.isDead) {
|
||||||
this.getScene().killEntity(this, killerId);
|
this.getScene().killEntity(this, killerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkIfDead() {
|
||||||
|
if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
||||||
|
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
|
||||||
|
this.isDead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the Lua callbacks for {@link EntityDamageEvent}.
|
* Runs the Lua callbacks for {@link EntityDamageEvent}.
|
||||||
*
|
*
|
||||||
@ -330,6 +338,8 @@ public abstract class GameEntity {
|
|||||||
if (entityController != null) {
|
if (entityController != null) {
|
||||||
entityController.onDie(this, getLastAttackType());
|
entityController.onDie(this, getLastAttackType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isDead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invoked when a global ability value is updated. */
|
/** Invoked when a global ability value is updated. */
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package emu.grasscutter.game.entity.gadget;
|
package emu.grasscutter.game.entity.gadget;
|
||||||
|
|
||||||
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
|
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||||
import emu.grasscutter.game.entity.EntityGadget;
|
import emu.grasscutter.game.entity.EntityGadget;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
|
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
|
||||||
@ -18,7 +18,7 @@ public final class GadgetRewardStatue extends GadgetContent {
|
|||||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||||
var dungeonManager = player.getScene().getDungeonManager();
|
var dungeonManager = player.getScene().getDungeonManager();
|
||||||
|
|
||||||
if (player.getScene().getChallenge() instanceof DungeonChallenge) {
|
if (player.getScene().getChallenge() instanceof WorldChallenge) {
|
||||||
var useCondensed =
|
var useCondensed =
|
||||||
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE;
|
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE;
|
||||||
dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId());
|
dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId());
|
||||||
|
@ -17,6 +17,7 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -55,6 +56,7 @@ public class GameHome {
|
|||||||
Set<Integer> unlockedHomeBgmList;
|
Set<Integer> unlockedHomeBgmList;
|
||||||
int enterHomeOption;
|
int enterHomeOption;
|
||||||
Map<Integer, Set<Integer>> finishedTalkIdMap;
|
Map<Integer, Set<Integer>> finishedTalkIdMap;
|
||||||
|
Set<Integer> finishedRewardEventIdSet;
|
||||||
|
|
||||||
public static GameHome getByUid(Integer uid) {
|
public static GameHome getByUid(Integer uid) {
|
||||||
var home = DatabaseHelper.getHomeByUid(uid);
|
var home = DatabaseHelper.getHomeByUid(uid);
|
||||||
@ -62,7 +64,9 @@ public class GameHome {
|
|||||||
home = GameHome.create(uid);
|
home = GameHome.create(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
home.reassignIfNull();
|
||||||
home.fixMainHouseIfOld();
|
home.fixMainHouseIfOld();
|
||||||
|
home.syncHomeAvatarCostume();
|
||||||
|
|
||||||
return home;
|
return home;
|
||||||
}
|
}
|
||||||
@ -79,9 +83,19 @@ public class GameHome {
|
|||||||
.mainHouseMap(new ConcurrentHashMap<>())
|
.mainHouseMap(new ConcurrentHashMap<>())
|
||||||
.unlockedHomeBgmList(new HashSet<>())
|
.unlockedHomeBgmList(new HashSet<>())
|
||||||
.finishedTalkIdMap(new HashMap<>())
|
.finishedTalkIdMap(new HashMap<>())
|
||||||
|
.finishedRewardEventIdSet(new HashSet<>())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// avoid NPE caused by database remover.
|
||||||
|
private void reassignIfNull() {
|
||||||
|
this.getSceneMap().values().stream()
|
||||||
|
.map(HomeSceneItem::getBlockItems)
|
||||||
|
.map(Map::values)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.forEach(HomeBlockItem::reassignIfNull);
|
||||||
|
}
|
||||||
|
|
||||||
// Data fixer.
|
// Data fixer.
|
||||||
private void fixMainHouseIfOld() {
|
private void fixMainHouseIfOld() {
|
||||||
if (this.getMainHouseMap() == null) {
|
if (this.getMainHouseMap() == null) {
|
||||||
@ -97,6 +111,18 @@ public class GameHome {
|
|||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void syncHomeAvatarCostume() {
|
||||||
|
Stream.of(this.sceneMap, this.mainHouseMap)
|
||||||
|
.map(ConcurrentHashMap::values)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(HomeSceneItem::getBlockItems)
|
||||||
|
.map(Map::values)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(HomeBlockItem::getDeployNPCList)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.forEach(npc -> npc.setCostumeId(this.getPlayer().getCostumeFrom(npc.getAvatarId())));
|
||||||
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
DatabaseHelper.saveHome(this);
|
DatabaseHelper.saveHome(this);
|
||||||
}
|
}
|
||||||
@ -113,12 +139,12 @@ public class GameHome {
|
|||||||
if (defaultItem != null) {
|
if (defaultItem != null) {
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
|
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
|
||||||
return HomeSceneItem.parseFrom(defaultItem, sceneId);
|
|
||||||
} else {
|
} else {
|
||||||
// Realm res missing bricks account, use default realm data to allow main house
|
// Realm res missing bricks account, use default realm data to allow main house
|
||||||
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
|
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
|
||||||
return HomeSceneItem.parseFrom(defaultItem, sceneId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return HomeSceneItem.parseFrom(defaultItem, sceneId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +175,8 @@ public class GameHome {
|
|||||||
this.getMainHouseMap().remove(outdoor); // delete main house in current scene.
|
this.getMainHouseMap().remove(outdoor); // delete main house in current scene.
|
||||||
this.getMainHouseItem(outdoor); // put new main house with default arrangement.
|
this.getMainHouseItem(outdoor); // put new main house with default arrangement.
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
|
this.getPlayer().getCurHomeWorld().getModuleManager().refreshMainHouse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onOwnerLogin(Player player) {
|
public void onOwnerLogin(Player player) {
|
||||||
@ -160,6 +188,8 @@ public class GameHome {
|
|||||||
player.getSession().send(new PacketHomeMarkPointNotify(player));
|
player.getSession().send(new PacketHomeMarkPointNotify(player));
|
||||||
player.getSession().send(new PacketHomeAvatarTalkFinishInfoNotify(player));
|
player.getSession().send(new PacketHomeAvatarTalkFinishInfoNotify(player));
|
||||||
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
|
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
|
||||||
|
player.getSession().send(new PacketHomeAvatarRewardEventNotify(player));
|
||||||
|
player.getSession().send(new PacketHomeAvatarAllFinishRewardNotify(player));
|
||||||
checkAccumulatedResources(player);
|
checkAccumulatedResources(player);
|
||||||
player.getSession().send(new PacketHomeResourceNotify(player));
|
player.getSession().send(new PacketHomeResourceNotify(player));
|
||||||
}
|
}
|
||||||
@ -226,6 +256,20 @@ public class GameHome {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean onClaimAvatarRewards(int eventId) {
|
||||||
|
if (this.finishedRewardEventIdSet == null) {
|
||||||
|
this.finishedRewardEventIdSet = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = this.finishedRewardEventIdSet.add(eventId);
|
||||||
|
this.save();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRewardEventFinished(int eventId) {
|
||||||
|
return this.finishedRewardEventIdSet != null && this.finishedRewardEventIdSet.contains(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean addUnlockedHomeBgm(int homeBgmId) {
|
public boolean addUnlockedHomeBgm(int homeBgmId) {
|
||||||
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
|
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
|
||||||
|
|
||||||
@ -404,7 +448,7 @@ public class GameHome {
|
|||||||
newCoin = storedCoin + owedCoin;
|
newCoin = storedCoin + owedCoin;
|
||||||
}
|
}
|
||||||
// Ensure max is not exceeded
|
// Ensure max is not exceeded
|
||||||
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
|
storedCoin = Math.min(maxCoin, newCoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fetter exp
|
// Update fetter exp
|
||||||
@ -416,7 +460,7 @@ public class GameHome {
|
|||||||
newFetter = storedFetterExp + owedFetter;
|
newFetter = storedFetterExp + owedFetter;
|
||||||
}
|
}
|
||||||
// Ensure max is not exceeded
|
// Ensure max is not exceeded
|
||||||
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
|
storedFetterExp = Math.min(maxFetter, newFetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
save();
|
save();
|
||||||
|
@ -2,6 +2,8 @@ package emu.grasscutter.game.home;
|
|||||||
|
|
||||||
import dev.morphia.annotations.*;
|
import dev.morphia.annotations.*;
|
||||||
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
|
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
|
||||||
|
import emu.grasscutter.game.home.suite.HomeSuiteItem;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
|
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -19,6 +21,7 @@ public class HomeBlockItem {
|
|||||||
List<HomeFurnitureItem> persistentFurnitureList;
|
List<HomeFurnitureItem> persistentFurnitureList;
|
||||||
List<HomeAnimalItem> deployAnimalList;
|
List<HomeAnimalItem> deployAnimalList;
|
||||||
List<HomeNPCItem> deployNPCList;
|
List<HomeNPCItem> deployNPCList;
|
||||||
|
List<HomeSuiteItem> suiteList;
|
||||||
|
|
||||||
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
|
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
|
||||||
// create from default setting
|
// create from default setting
|
||||||
@ -37,10 +40,11 @@ public class HomeBlockItem {
|
|||||||
.toList())
|
.toList())
|
||||||
.deployAnimalList(List.of())
|
.deployAnimalList(List.of())
|
||||||
.deployNPCList(List.of())
|
.deployNPCList(List.of())
|
||||||
|
.suiteList(List.of())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
|
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo, Player owner) {
|
||||||
this.blockId = homeBlockArrangementInfo.getBlockId();
|
this.blockId = homeBlockArrangementInfo.getBlockId();
|
||||||
|
|
||||||
this.deployFurnitureList =
|
this.deployFurnitureList =
|
||||||
@ -60,7 +64,12 @@ public class HomeBlockItem {
|
|||||||
|
|
||||||
this.deployNPCList =
|
this.deployNPCList =
|
||||||
homeBlockArrangementInfo.getDeployNpcListList().stream()
|
homeBlockArrangementInfo.getDeployNpcListList().stream()
|
||||||
.map(HomeNPCItem::parseFrom)
|
.map(homeNpcData -> HomeNPCItem.parseFrom(homeNpcData, owner))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
this.suiteList =
|
||||||
|
homeBlockArrangementInfo.getFurnitureSuiteListList().stream()
|
||||||
|
.map(HomeSuiteItem::parseFrom)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,15 +90,20 @@ public class HomeBlockItem {
|
|||||||
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
|
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
|
||||||
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
|
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
|
||||||
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
|
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
|
||||||
|
this.suiteList.forEach(f -> proto.addFurnitureSuiteList(f.toProto()));
|
||||||
|
|
||||||
return proto.build();
|
return proto.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add more types (farm field and suite)
|
// TODO implement farm field.
|
||||||
public List<? extends HomeMarkPointProtoFactory> getMarkPointProtoFactories() {
|
public List<? extends HomeMarkPointProtoFactory> getMarkPointProtoFactories() {
|
||||||
this.reassignIfNull();
|
this.reassignIfNull();
|
||||||
|
|
||||||
return Stream.of(this.deployFurnitureList, this.persistentFurnitureList, this.deployNPCList)
|
return Stream.of(
|
||||||
|
this.deployFurnitureList,
|
||||||
|
this.persistentFurnitureList,
|
||||||
|
this.deployNPCList,
|
||||||
|
this.suiteList)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -107,5 +121,8 @@ public class HomeBlockItem {
|
|||||||
if (this.deployNPCList == null) {
|
if (this.deployNPCList == null) {
|
||||||
this.deployNPCList = List.of();
|
this.deployNPCList = List.of();
|
||||||
}
|
}
|
||||||
|
if (this.suiteList == null) {
|
||||||
|
this.suiteList = List.of();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
239
src/main/java/emu/grasscutter/game/home/HomeModuleManager.java
Normal file
239
src/main/java/emu/grasscutter/game/home/HomeModuleManager.java
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
package emu.grasscutter.game.home;
|
||||||
|
|
||||||
|
import com.github.davidmoten.guavamini.Lists;
|
||||||
|
import emu.grasscutter.game.home.suite.event.HomeAvatarRewardEvent;
|
||||||
|
import emu.grasscutter.game.home.suite.event.HomeAvatarSummonEvent;
|
||||||
|
import emu.grasscutter.game.home.suite.event.SuiteEventType;
|
||||||
|
import emu.grasscutter.game.inventory.GameItem;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.net.proto.HomeAvatarRewardEventNotifyOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.HomeAvatarSummonAllEventNotifyOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.RetcodeOuterClass;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonAllEventNotify;
|
||||||
|
import emu.grasscutter.utils.Either;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class HomeModuleManager {
|
||||||
|
final Player homeOwner;
|
||||||
|
final HomeWorld homeWorld;
|
||||||
|
final GameHome home;
|
||||||
|
final int moduleId;
|
||||||
|
final HomeScene outdoor;
|
||||||
|
HomeScene indoor;
|
||||||
|
final List<HomeAvatarRewardEvent> rewardEvents;
|
||||||
|
final List<HomeAvatarSummonEvent> summonEvents;
|
||||||
|
|
||||||
|
public HomeModuleManager(HomeWorld homeWorld) {
|
||||||
|
this.homeOwner = homeWorld.getHost();
|
||||||
|
this.homeWorld = homeWorld;
|
||||||
|
this.home = homeWorld.getHome();
|
||||||
|
this.moduleId = this.homeOwner.getCurrentRealmId();
|
||||||
|
this.outdoor = homeWorld.getSceneById(homeWorld.getActiveOutdoorSceneId());
|
||||||
|
this.refreshMainHouse();
|
||||||
|
this.rewardEvents = Lists.newArrayList();
|
||||||
|
this.summonEvents = Collections.synchronizedList(Lists.newArrayList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
if (this.moduleId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outdoor.onTick();
|
||||||
|
this.indoor.onTick();
|
||||||
|
this.summonEvents.removeIf(HomeAvatarSummonEvent::isTimeOver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshMainHouse() {
|
||||||
|
if (this.moduleId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.indoor = this.homeWorld.getSceneById(this.homeWorld.getActiveIndoorSceneId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUpdateArrangement() {
|
||||||
|
this.fireAllAvatarRewardEvents();
|
||||||
|
this.cancelSummonEventsIfAvatarLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireAllAvatarRewardEvents() {
|
||||||
|
this.rewardEvents.clear();
|
||||||
|
var allBlockItems =
|
||||||
|
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
||||||
|
.map(HomeSceneItem::getBlockItems)
|
||||||
|
.map(Map::values)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
var suites =
|
||||||
|
allBlockItems.stream()
|
||||||
|
.map(HomeBlockItem::getSuiteList)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
allBlockItems.stream()
|
||||||
|
.map(HomeBlockItem::getDeployNPCList)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.forEach(
|
||||||
|
avatar -> {
|
||||||
|
suites.forEach(
|
||||||
|
suite -> {
|
||||||
|
var data =
|
||||||
|
SuiteEventType.HOME_AVATAR_REWARD_EVENT.getEventDataFrom(
|
||||||
|
avatar.getAvatarId(), suite.getSuiteId());
|
||||||
|
if (data == null || this.home.isRewardEventFinished(data.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rewardEvents.add(
|
||||||
|
new HomeAvatarRewardEvent(
|
||||||
|
homeOwner,
|
||||||
|
data.getId(),
|
||||||
|
data.getRewardID(),
|
||||||
|
data.getAvatarID(),
|
||||||
|
data.getSuiteId(),
|
||||||
|
suite.getGuid()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.summonEvents != null) {
|
||||||
|
var suiteIdList = this.rewardEvents.stream().map(HomeAvatarRewardEvent::getSuiteId).toList();
|
||||||
|
this.summonEvents.removeIf(event -> suiteIdList.contains(event.getSuiteId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelSummonEventsIfAvatarLeave() {
|
||||||
|
var avatars =
|
||||||
|
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
||||||
|
.map(HomeSceneItem::getBlockItems)
|
||||||
|
.map(Map::values)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(HomeBlockItem::getDeployNPCList)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(HomeNPCItem::getAvatarId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
this.summonEvents.removeIf(event -> !avatars.contains(event.getAvatarId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Either<List<GameItem>, Integer> claimAvatarRewards(int eventId) {
|
||||||
|
if (this.rewardEvents.isEmpty()) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = this.rewardEvents.remove(0);
|
||||||
|
if (event.getEventId() != eventId) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.homeOwner.getHome().onClaimAvatarRewards(eventId)) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Either.left(event.giveRewards());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Either<HomeAvatarSummonEvent, Integer> fireAvatarSummonEvent(
|
||||||
|
Player owner, int avatarId, int guid, int suiteId) {
|
||||||
|
var targetSuite =
|
||||||
|
((HomeScene) owner.getScene())
|
||||||
|
.getSceneItem().getBlockItems().values().stream()
|
||||||
|
.map(HomeBlockItem::getSuiteList)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.filter(suite -> suite.getGuid() == guid)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (this.isInRewardEvent(avatarId)) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_DUPLICATE_AVATAR_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rewardEvents.stream().anyMatch(event -> event.getGuid() == guid)) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_FURNITURE_GUID_ERROR_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.summonEvents.removeIf(event -> event.getGuid() == guid || event.getAvatarId() == avatarId);
|
||||||
|
|
||||||
|
if (targetSuite == null) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventData = SuiteEventType.HOME_AVATAR_SUMMON_EVENT.getEventDataFrom(avatarId, suiteId);
|
||||||
|
if (eventData == null) {
|
||||||
|
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
var event =
|
||||||
|
new HomeAvatarSummonEvent(
|
||||||
|
owner, eventData.getId(), eventData.getRewardID(), avatarId, suiteId, guid);
|
||||||
|
this.summonEvents.add(event);
|
||||||
|
owner.sendPacket(new PacketHomeAvatarSummonAllEventNotify(owner));
|
||||||
|
return Either.left(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFinishSummonEvent(int eventId) {
|
||||||
|
this.summonEvents.removeIf(event -> event.getEventId() == eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify toRewardEventProto() {
|
||||||
|
var notify = HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify.newBuilder();
|
||||||
|
if (!this.rewardEvents.isEmpty()) {
|
||||||
|
notify.setRewardEvent(this.rewardEvents.get(0).toProto()).setIsEventTrigger(true);
|
||||||
|
|
||||||
|
notify.addAllPendingList(
|
||||||
|
this.rewardEvents.subList(1, this.rewardEvents.size()).stream()
|
||||||
|
.map(HomeAvatarRewardEvent::toProto)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return notify.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify
|
||||||
|
toSummonEventProto() {
|
||||||
|
return HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify.newBuilder()
|
||||||
|
.addAllSummonEventList(
|
||||||
|
this.summonEvents.stream().map(HomeAvatarSummonEvent::toProto).toList())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInRewardEvent(int avatarId) {
|
||||||
|
return this.rewardEvents.stream().anyMatch(e -> e.getAvatarId() == avatarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeSceneItem getOutdoorSceneItem() {
|
||||||
|
return this.outdoor.getSceneItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeSceneItem getIndoorSceneItem() {
|
||||||
|
return this.indoor.getSceneItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetModule() {
|
||||||
|
if (this.moduleId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outdoor.addEntities(this.getOutdoorSceneItem().getAnimals(this.outdoor));
|
||||||
|
this.indoor.addEntities(this.getIndoorSceneItem().getAnimals(this.indoor));
|
||||||
|
this.fireAllAvatarRewardEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRemovedModule() {
|
||||||
|
if (this.moduleId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outdoor.getEntities().clear();
|
||||||
|
this.indoor.getEntities().clear();
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package emu.grasscutter.game.home;
|
|||||||
|
|
||||||
import dev.morphia.annotations.Entity;
|
import dev.morphia.annotations.Entity;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.world.Position;
|
import emu.grasscutter.game.world.Position;
|
||||||
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
|
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
|
||||||
import emu.grasscutter.net.proto.HomeMarkPointNPCDataOuterClass;
|
import emu.grasscutter.net.proto.HomeMarkPointNPCDataOuterClass;
|
||||||
@ -23,11 +24,12 @@ public class HomeNPCItem implements HomeMarkPointProtoFactory {
|
|||||||
Position spawnRot;
|
Position spawnRot;
|
||||||
int costumeId;
|
int costumeId;
|
||||||
|
|
||||||
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
|
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData, Player owner) {
|
||||||
return HomeNPCItem.of()
|
return HomeNPCItem.of()
|
||||||
.avatarId(homeNpcData.getAvatarId())
|
.avatarId(homeNpcData.getAvatarId())
|
||||||
.spawnPos(new Position(homeNpcData.getSpawnPos()))
|
.spawnPos(new Position(homeNpcData.getSpawnPos()))
|
||||||
.spawnRot(new Position(homeNpcData.getSpawnRot()))
|
.spawnRot(new Position(homeNpcData.getSpawnRot()))
|
||||||
|
.costumeId(owner.getCostumeFrom(homeNpcData.getAvatarId()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package emu.grasscutter.game.home;
|
package emu.grasscutter.game.home;
|
||||||
|
|
||||||
import emu.grasscutter.data.excels.scene.SceneData;
|
import emu.grasscutter.data.excels.scene.SceneData;
|
||||||
|
import emu.grasscutter.game.entity.EntityHomeAnimal;
|
||||||
import emu.grasscutter.game.entity.GameEntity;
|
import emu.grasscutter.game.entity.GameEntity;
|
||||||
|
import emu.grasscutter.game.entity.Rebornable;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.world.Scene;
|
import emu.grasscutter.game.world.Scene;
|
||||||
|
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||||
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
|
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
|
||||||
|
|
||||||
public class HomeScene extends Scene {
|
public class HomeScene extends Scene {
|
||||||
@ -40,10 +43,31 @@ public class HomeScene extends Scene {
|
|||||||
.forEach(gameEntity -> gameEntity.onTick(this.getSceneTimeSeconds()));
|
.forEach(gameEntity -> gameEntity.onTick(this.getSceneTimeSeconds()));
|
||||||
|
|
||||||
this.finishLoading();
|
this.finishLoading();
|
||||||
this.checkPlayerRespawn();
|
|
||||||
if (this.tickCount++ % 10 == 0) this.broadcastPacket(new PacketSceneTimeNotify(this));
|
if (this.tickCount++ % 10 == 0) this.broadcastPacket(new PacketSceneTimeNotify(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onEnterEditModeFinish() {
|
||||||
|
this.removeEntities(
|
||||||
|
this.getEntities().values().stream()
|
||||||
|
.filter(gameEntity -> gameEntity instanceof EntityHomeAnimal)
|
||||||
|
.toList(),
|
||||||
|
VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLeaveEditMode() {
|
||||||
|
this.addEntities(this.getSceneItem().getAnimals(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void killEntity(GameEntity target, int attackerId) {
|
||||||
|
if (target instanceof Rebornable rebornable) {
|
||||||
|
rebornable.onAiKillSelf(); // Teapot animals will not die. They will revive!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.killEntity(target, attackerId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkNpcGroup() {}
|
public void checkNpcGroup() {}
|
||||||
|
|
||||||
|
@ -6,16 +6,18 @@ import emu.grasscutter.Grasscutter;
|
|||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
|
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
|
||||||
import emu.grasscutter.game.entity.EntityHomeAnimal;
|
import emu.grasscutter.game.entity.EntityHomeAnimal;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.world.Position;
|
import emu.grasscutter.game.world.Position;
|
||||||
import emu.grasscutter.game.world.Scene;
|
import emu.grasscutter.game.world.Scene;
|
||||||
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
|
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import lombok.*;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -49,14 +51,14 @@ public class HomeSceneItem {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(HomeSceneArrangementInfo arrangementInfo) {
|
public void update(HomeSceneArrangementInfo arrangementInfo, Player owner) {
|
||||||
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
|
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
|
||||||
var block = this.blockItems.get(blockItem.getBlockId());
|
var block = this.blockItems.get(blockItem.getBlockId());
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
|
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
block.update(blockItem);
|
block.update(blockItem, owner);
|
||||||
this.blockItems.put(blockItem.getBlockId(), block);
|
this.blockItems.put(blockItem.getBlockId(), block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,17 +86,13 @@ public class HomeSceneItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable public Position getTeleportPointPos(int guid) {
|
@Nullable public Position getTeleportPointPos(int guid) {
|
||||||
var pos = new AtomicReference<Position>();
|
return this.getBlockItems().values().stream()
|
||||||
|
|
||||||
this.getBlockItems().values().stream()
|
|
||||||
.map(HomeBlockItem::getDeployFurnitureList)
|
.map(HomeBlockItem::getDeployFurnitureList)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.filter(homeFurnitureItem -> homeFurnitureItem.getGuid() == guid)
|
.filter(homeFurnitureItem -> homeFurnitureItem.getGuid() == guid)
|
||||||
.map(HomeFurnitureItem::getSpawnPos)
|
.map(HomeFurnitureItem::getSpawnPos)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.ifPresent(pos::set);
|
.orElse(null);
|
||||||
|
|
||||||
return pos.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EntityHomeAnimal> getAnimals(Scene scene) {
|
public List<EntityHomeAnimal> getAnimals(Scene scene) {
|
||||||
@ -109,7 +107,8 @@ public class HomeSceneItem {
|
|||||||
return new EntityHomeAnimal(
|
return new EntityHomeAnimal(
|
||||||
scene,
|
scene,
|
||||||
GameData.getHomeWorldAnimalDataMap().get(homeAnimalItem.getFurnitureId()),
|
GameData.getHomeWorldAnimalDataMap().get(homeAnimalItem.getFurnitureId()),
|
||||||
homeAnimalItem.getSpawnPos());
|
homeAnimalItem.getSpawnPos(),
|
||||||
|
homeAnimalItem.getSpawnRot());
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@ -8,33 +8,65 @@ import emu.grasscutter.game.world.World;
|
|||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.proto.ChatInfoOuterClass;
|
import emu.grasscutter.net.proto.ChatInfoOuterClass;
|
||||||
import emu.grasscutter.server.game.GameServer;
|
import emu.grasscutter.server.game.GameServer;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class HomeWorld extends World {
|
public class HomeWorld extends World {
|
||||||
@Getter private final GameHome home;
|
private final GameHome home;
|
||||||
|
private HomeModuleManager moduleManager;
|
||||||
|
|
||||||
public HomeWorld(GameServer server, Player owner) {
|
public HomeWorld(GameServer server, Player owner) {
|
||||||
super(server, owner);
|
super(server, owner);
|
||||||
|
|
||||||
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
|
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
|
||||||
server.registerHomeWorld(this);
|
this.refreshModuleManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerScene(Scene scene) {
|
public boolean onTick() {
|
||||||
this.addAnimalsToScene((HomeScene) scene);
|
if (this.moduleManager == null) {
|
||||||
super.registerScene(scene);
|
return false;
|
||||||
|
}
|
||||||
|
this.moduleManager.tick();
|
||||||
|
|
||||||
|
if (this.getTickCount() % 10 == 0) {
|
||||||
|
this.getPlayers().forEach(p -> p.sendPacket(new PacketPlayerGameTimeNotify(p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isInHome(this.getHost()) && this.getTickCount() % 60 == 0) {
|
||||||
|
this.getHost().updatePlayerGameTime(this.getCurrentWorldTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tickCount++;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void refreshModuleManager() {
|
||||||
public void deregisterScene(Scene scene) {
|
if (this.moduleManager != null) {
|
||||||
super.deregisterScene(scene);
|
this.moduleManager.onRemovedModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moduleManager = new HomeModuleManager(this);
|
||||||
|
this.moduleManager.onSetModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAnimalsToScene(HomeScene scene) {
|
public int getActiveOutdoorSceneId() {
|
||||||
scene.getSceneItem().getAnimals(scene).forEach(scene::addEntity);
|
return this.getHost().getCurrentRealmId() + 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActiveIndoorSceneId() {
|
||||||
|
return this.isRealmIdValid()
|
||||||
|
? this.getSceneById(this.getActiveOutdoorSceneId()).getSceneItem().getRoomSceneId()
|
||||||
|
: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRealmIdValid() {
|
||||||
|
return this.getHost().getCurrentRealmId() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -188,6 +220,12 @@ public class HomeWorld extends World {
|
|||||||
return this.getPlayers().contains(player);
|
return this.getPlayers().contains(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ifHost(Player hostOrGuest, Consumer<Player> ifHost) {
|
||||||
|
if (this.getHost().equals(hostOrGuest)) {
|
||||||
|
ifHost.accept(hostOrGuest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void sendPacketToHostIfOnline(BasePacket basePacket) {
|
public void sendPacketToHostIfOnline(BasePacket basePacket) {
|
||||||
if (this.getHost().isOnline()) {
|
if (this.getHost().isOnline()) {
|
||||||
this.getHost().sendPacket(basePacket);
|
this.getHost().sendPacket(basePacket);
|
||||||
|
@ -5,10 +5,7 @@ import emu.grasscutter.game.props.EnterReason;
|
|||||||
import emu.grasscutter.game.world.Position;
|
import emu.grasscutter.game.world.Position;
|
||||||
import emu.grasscutter.game.world.World;
|
import emu.grasscutter.game.world.World;
|
||||||
import emu.grasscutter.game.world.data.TeleportProperties;
|
import emu.grasscutter.game.world.data.TeleportProperties;
|
||||||
import emu.grasscutter.net.proto.EnterTypeOuterClass;
|
import emu.grasscutter.net.proto.*;
|
||||||
import emu.grasscutter.net.proto.OtherPlayerEnterHomeNotifyOuterClass;
|
|
||||||
import emu.grasscutter.net.proto.PlayerApplyEnterHomeResultNotifyOuterClass;
|
|
||||||
import emu.grasscutter.net.proto.RetcodeOuterClass;
|
|
||||||
import emu.grasscutter.server.event.player.PlayerEnterHomeEvent;
|
import emu.grasscutter.server.event.player.PlayerEnterHomeEvent;
|
||||||
import emu.grasscutter.server.event.player.PlayerLeaveHomeEvent;
|
import emu.grasscutter.server.event.player.PlayerLeaveHomeEvent;
|
||||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||||
@ -215,6 +212,10 @@ public class HomeWorldMPSystem extends BaseGameSystem {
|
|||||||
player.setCurHomeWorld(myHome);
|
player.setCurHomeWorld(myHome);
|
||||||
myHome.getHome().onOwnerLogin(player);
|
myHome.getHome().onOwnerLogin(player);
|
||||||
|
|
||||||
|
player.sendPacket(
|
||||||
|
new PacketPlayerQuitFromHomeNotify(
|
||||||
|
PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason
|
||||||
|
.BACK_TO_MY_WORLD));
|
||||||
player.sendPacket(
|
player.sendPacket(
|
||||||
new PacketPlayerEnterSceneNotify(
|
new PacketPlayerEnterSceneNotify(
|
||||||
player,
|
player,
|
||||||
@ -263,6 +264,9 @@ public class HomeWorldMPSystem extends BaseGameSystem {
|
|||||||
victim.setCurHomeWorld(myHome);
|
victim.setCurHomeWorld(myHome);
|
||||||
myHome.getHome().onOwnerLogin(victim);
|
myHome.getHome().onOwnerLogin(victim);
|
||||||
|
|
||||||
|
victim.sendPacket(
|
||||||
|
new PacketPlayerQuitFromHomeNotify(
|
||||||
|
PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason.KICK_BY_HOST));
|
||||||
victim.sendPacket(
|
victim.sendPacket(
|
||||||
new PacketPlayerEnterSceneNotify(
|
new PacketPlayerEnterSceneNotify(
|
||||||
victim,
|
victim,
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package emu.grasscutter.game.home.suite;
|
||||||
|
|
||||||
|
import dev.morphia.annotations.Entity;
|
||||||
|
import emu.grasscutter.game.home.HomeMarkPointProtoFactory;
|
||||||
|
import emu.grasscutter.game.home.SpecialFurnitureType;
|
||||||
|
import emu.grasscutter.game.world.Position;
|
||||||
|
import emu.grasscutter.net.proto.HomeFurnitureSuiteDataOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
|
||||||
|
import emu.grasscutter.net.proto.HomeMarkPointSuiteDataOuterClass;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Builder(builderMethodName = "of")
|
||||||
|
@Getter
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class HomeSuiteItem implements HomeMarkPointProtoFactory {
|
||||||
|
public static final int SUITE_FURNITURE_ID = 377101;
|
||||||
|
int guid;
|
||||||
|
int suiteId;
|
||||||
|
Position pos;
|
||||||
|
List<Integer> includedFurnitureIndexList;
|
||||||
|
boolean isAllowSummon;
|
||||||
|
|
||||||
|
public static HomeSuiteItem parseFrom(
|
||||||
|
HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData data) {
|
||||||
|
return HomeSuiteItem.of()
|
||||||
|
.guid(data.getGuid())
|
||||||
|
.suiteId(data.getSuiteId())
|
||||||
|
.pos(new Position(data.getSpawnPos()))
|
||||||
|
.includedFurnitureIndexList(data.getIncludedFurnitureIndexListList())
|
||||||
|
.isAllowSummon(data.getIsAllowSummon())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData toProto() {
|
||||||
|
return HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData.newBuilder()
|
||||||
|
.setSuiteId(this.suiteId)
|
||||||
|
.setGuid(this.guid)
|
||||||
|
.setIsAllowSummon(this.isAllowSummon)
|
||||||
|
.addAllIncludedFurnitureIndexList(this.includedFurnitureIndexList)
|
||||||
|
.setSpawnPos(this.pos.toProto())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable @Override
|
||||||
|
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto() {
|
||||||
|
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
|
||||||
|
.setFurnitureId(SUITE_FURNITURE_ID)
|
||||||
|
.setPos(this.pos.toProto())
|
||||||
|
.setFurnitureType(this.getType().getValue())
|
||||||
|
.setGuid(this.guid)
|
||||||
|
.setSuiteData(
|
||||||
|
HomeMarkPointSuiteDataOuterClass.HomeMarkPointSuiteData.newBuilder()
|
||||||
|
.setSuiteId(this.suiteId)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpecialFurnitureType getType() {
|
||||||
|
return SpecialFurnitureType.FurnitureSuite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
HomeSuiteItem that = (HomeSuiteItem) o;
|
||||||
|
return suiteId == that.suiteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(suiteId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package emu.grasscutter.game.home.suite.event;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.inventory.GameItem;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.utils.Utils;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public abstract class HomeAvatarEvent {
|
||||||
|
final Player homeOwner;
|
||||||
|
final int eventId;
|
||||||
|
final int rewardId;
|
||||||
|
final int avatarId;
|
||||||
|
final int suiteId;
|
||||||
|
final int guid;
|
||||||
|
final int randomPos;
|
||||||
|
|
||||||
|
public HomeAvatarEvent(
|
||||||
|
Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
|
||||||
|
this.homeOwner = homeOwner;
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.rewardId = rewardId;
|
||||||
|
this.avatarId = avatarId;
|
||||||
|
this.suiteId = suiteId;
|
||||||
|
this.guid = guid;
|
||||||
|
this.randomPos = this.generateRandomPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int generateRandomPos() {
|
||||||
|
return Utils.randomRange(1, 97);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GameItem> giveRewards() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
HomeAvatarEvent that = (HomeAvatarEvent) o;
|
||||||
|
return eventId == that.eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(eventId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package emu.grasscutter.game.home.suite.event;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.game.inventory.GameItem;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.game.props.ActionReason;
|
||||||
|
import emu.grasscutter.net.proto.HomeAvatarRewardEventInfoOuterClass;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HomeAvatarRewardEvent extends HomeAvatarEvent {
|
||||||
|
public HomeAvatarRewardEvent(
|
||||||
|
Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
|
||||||
|
super(homeOwner, eventId, rewardId, avatarId, suiteId, guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeAvatarRewardEventInfoOuterClass.HomeAvatarRewardEventInfo toProto() {
|
||||||
|
return HomeAvatarRewardEventInfoOuterClass.HomeAvatarRewardEventInfo.newBuilder()
|
||||||
|
.setAvatarId(this.getAvatarId())
|
||||||
|
.setEventId(this.getEventId())
|
||||||
|
.setGuid(this.getGuid())
|
||||||
|
.setSuiteId(this.getSuiteId())
|
||||||
|
.setRandomPosition(this.getRandomPos())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GameItem> giveRewards() {
|
||||||
|
var data = GameData.getRewardDataMap().get(this.getRewardId());
|
||||||
|
if (data == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
var rewards = data.getRewardItemList().stream().map(GameItem::new).toList();
|
||||||
|
this.getHomeOwner().getInventory().addItems(rewards, ActionReason.HomeAvatarEventReward);
|
||||||
|
return rewards;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package emu.grasscutter.game.home.suite.event;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.net.proto.HomeAvatarSummonEventInfoOuterClass;
|
||||||
|
import emu.grasscutter.utils.Utils;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class HomeAvatarSummonEvent extends HomeAvatarEvent {
|
||||||
|
public static final int TIME_LIMIT_SECS = 240;
|
||||||
|
final int eventOverTime;
|
||||||
|
|
||||||
|
public HomeAvatarSummonEvent(
|
||||||
|
Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
|
||||||
|
super(homeOwner, eventId, rewardId, avatarId, suiteId, guid);
|
||||||
|
|
||||||
|
this.eventOverTime = Utils.getCurrentSeconds() + TIME_LIMIT_SECS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomeAvatarSummonEventInfoOuterClass.HomeAvatarSummonEventInfo toProto() {
|
||||||
|
return HomeAvatarSummonEventInfoOuterClass.HomeAvatarSummonEventInfo.newBuilder()
|
||||||
|
.setAvatarId(this.getAvatarId())
|
||||||
|
.setEventId(this.getEventId())
|
||||||
|
.setGuid(this.getGuid())
|
||||||
|
.setSuitId(this.getSuiteId())
|
||||||
|
.setRandomPosition(this.getRandomPos())
|
||||||
|
.setEventOverTime(this.eventOverTime)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTimeOver() {
|
||||||
|
return Utils.getCurrentSeconds() > this.eventOverTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package emu.grasscutter.game.home.suite.event;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.data.excels.HomeWorldEventData;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum SuiteEventType {
|
||||||
|
HOME_AVATAR_REWARD_EVENT,
|
||||||
|
HOME_AVATAR_SUMMON_EVENT;
|
||||||
|
|
||||||
|
@Nullable public HomeWorldEventData getEventDataFrom(int avatarId, int suiteId) {
|
||||||
|
return GameData.getHomeWorldEventDataMap().values().stream()
|
||||||
|
.filter(
|
||||||
|
data ->
|
||||||
|
data.getEventType() == this
|
||||||
|
&& data.getAvatarID() == avatarId
|
||||||
|
&& data.getSuiteId() == suiteId)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
@ -216,14 +216,14 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if the player has the item in their inventory. This is exact.
|
* Checks to see if the player has the item in their inventory. This is not exact.
|
||||||
*
|
*
|
||||||
* @param items A map of item game IDs to their count.
|
* @param items A map of item game IDs to their count.
|
||||||
* @return True if the player has the items, false otherwise.
|
* @return True if the player has the items, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean hasAllItems(Collection<ItemParam> items) {
|
public boolean hasAllItems(Collection<ItemParam> items) {
|
||||||
for (var item : items) {
|
for (var item : items) {
|
||||||
if (!this.hasItem(item.getItemId(), item.getCount(), true)) return false;
|
if (!this.hasItem(item.getItemId(), item.getCount(), false)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -104,7 +104,8 @@ public final class BlossomActivity {
|
|||||||
|
|
||||||
var monsterData = GameData.getMonsterDataMap().get((int) entry);
|
var monsterData = GameData.getMonsterDataMap().get((int) entry);
|
||||||
var level = scene.getEntityLevel(1, worldLevelOverride);
|
var level = scene.getEntityLevel(1, worldLevelOverride);
|
||||||
var entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
|
var entity =
|
||||||
|
new EntityMonster(scene, monsterData, pos.nearby2d(4f), Position.ZERO, level);
|
||||||
scene.addEntity(entity);
|
scene.addEntity(entity);
|
||||||
newMonsters.add(entity);
|
newMonsters.add(entity);
|
||||||
}
|
}
|
||||||
|
@ -394,10 +394,11 @@ public class EnergyManager extends BasePlayerManager {
|
|||||||
public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) {
|
public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) {
|
||||||
for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) {
|
for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) {
|
||||||
// giving the exact amount read off the AvatarSkillData.json
|
// giving the exact amount read off the AvatarSkillData.json
|
||||||
entityAvatar.addEnergy(
|
var skillDepot = entityAvatar.getAvatar().getSkillDepot();
|
||||||
entityAvatar.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal(),
|
if (skillDepot != null) {
|
||||||
changeReason,
|
entityAvatar.addEnergy(
|
||||||
isFlat);
|
skillDepot.getEnergySkillData().getCostElemVal(), changeReason, isFlat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
@Getter private Map<Integer, ActiveCookCompoundData> activeCookCompounds;
|
@Getter private Map<Integer, ActiveCookCompoundData> activeCookCompounds;
|
||||||
@Getter private Map<Integer, Integer> questGlobalVariables;
|
@Getter private Map<Integer, Integer> questGlobalVariables;
|
||||||
@Getter private Map<Integer, Integer> openStates;
|
@Getter private Map<Integer, Integer> openStates;
|
||||||
|
@Getter private Map<Integer, Set<Integer>> sceneTags;
|
||||||
@Getter @Setter private Map<Integer, Set<Integer>> unlockedSceneAreas;
|
@Getter @Setter private Map<Integer, Set<Integer>> unlockedSceneAreas;
|
||||||
@Getter @Setter private Map<Integer, Set<Integer>> unlockedScenePoints;
|
@Getter @Setter private Map<Integer, Set<Integer>> unlockedScenePoints;
|
||||||
@Getter @Setter private List<Integer> chatEmojiIdList;
|
@Getter @Setter private List<Integer> chatEmojiIdList;
|
||||||
@ -175,6 +176,7 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
@Getter @Setter private Set<Date> moonCardGetTimes;
|
@Getter @Setter private Set<Date> moonCardGetTimes;
|
||||||
|
|
||||||
@Transient @Getter private boolean paused;
|
@Transient @Getter private boolean paused;
|
||||||
|
@Transient @Getter @Setter private Future<?> queuedTeleport;
|
||||||
@Transient @Getter @Setter private int enterSceneToken;
|
@Transient @Getter @Setter private int enterSceneToken;
|
||||||
@Transient @Getter @Setter private SceneLoadState sceneLoadState = SceneLoadState.NONE;
|
@Transient @Getter @Setter private SceneLoadState sceneLoadState = SceneLoadState.NONE;
|
||||||
@Transient private boolean hasSentLoginPackets;
|
@Transient private boolean hasSentLoginPackets;
|
||||||
@ -244,6 +246,7 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
this.unlockedRecipies = new HashMap<>();
|
this.unlockedRecipies = new HashMap<>();
|
||||||
this.questGlobalVariables = new HashMap<>();
|
this.questGlobalVariables = new HashMap<>();
|
||||||
this.openStates = new HashMap<>();
|
this.openStates = new HashMap<>();
|
||||||
|
this.sceneTags = new HashMap<>();
|
||||||
this.unlockedSceneAreas = new HashMap<>();
|
this.unlockedSceneAreas = new HashMap<>();
|
||||||
this.unlockedScenePoints = new HashMap<>();
|
this.unlockedScenePoints = new HashMap<>();
|
||||||
this.chatEmojiIdList = new ArrayList<>();
|
this.chatEmojiIdList = new ArrayList<>();
|
||||||
@ -295,6 +298,7 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
this.codex = new PlayerCodex(this);
|
this.codex = new PlayerCodex(this);
|
||||||
|
|
||||||
this.applyProperties();
|
this.applyProperties();
|
||||||
|
this.applyStartingSceneTags();
|
||||||
this.getFlyCloakList().add(140001);
|
this.getFlyCloakList().add(140001);
|
||||||
this.getNameCardList().add(210001);
|
this.getNameCardList().add(210001);
|
||||||
|
|
||||||
@ -587,6 +591,20 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
this.getProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA));
|
this.getProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies all default scenetags to the player.
|
||||||
|
*/
|
||||||
|
private void applyStartingSceneTags() {
|
||||||
|
GameData.getSceneTagDataMap().values().stream()
|
||||||
|
.filter(sceneTag -> sceneTag.isDefaultValid())
|
||||||
|
.forEach(sceneTag -> {
|
||||||
|
if (this.getSceneTags().get(sceneTag.getSceneId()) == null) {
|
||||||
|
this.getSceneTags().put(sceneTag.getSceneId(), new HashSet<>());
|
||||||
|
}
|
||||||
|
this.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a property to the player if it doesn't exist in the database.
|
* Applies a property to the player if it doesn't exist in the database.
|
||||||
*
|
*
|
||||||
@ -950,6 +968,13 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
|
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCostumeFrom(int avatarId) {
|
||||||
|
var avatars = this.getAvatars();
|
||||||
|
avatars.loadFromDatabase();
|
||||||
|
var avatar = avatars.getAvatarById(avatarId);
|
||||||
|
return avatar == null ? 0 : avatar.getCostume();
|
||||||
|
}
|
||||||
|
|
||||||
public void addPersonalLine(int personalLineId) {
|
public void addPersonalLine(int personalLineId) {
|
||||||
this.getPersonalLineList().add(personalLineId);
|
this.getPersonalLineList().add(personalLineId);
|
||||||
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);
|
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);
|
||||||
@ -1354,14 +1379,6 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
this.getPlayerProgress().setPlayer(this); // Add reference to the player.
|
this.getPlayerProgress().setPlayer(this); // Add reference to the player.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the player selects their avatar.
|
|
||||||
*/
|
|
||||||
public void onPlayerBorn() {
|
|
||||||
Grasscutter.getThreadPool().submit(
|
|
||||||
this.getQuestManager()::onPlayerBorn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onLogin() {
|
public void onLogin() {
|
||||||
// Quest - Commented out because a problem is caused if you log out while this quest is active
|
// Quest - Commented out because a problem is caused if you log out while this quest is active
|
||||||
/*
|
/*
|
||||||
@ -1377,6 +1394,10 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Ensure the player has valid scenetags, allows old accounts to work
|
||||||
|
if (this.getSceneTags().isEmpty() || this.getSceneTags() == null) {
|
||||||
|
this.applyStartingSceneTags();
|
||||||
|
}
|
||||||
|
|
||||||
if (GameHome.HOME_SCENE_IDS.contains(this.getSceneId())) {
|
if (GameHome.HOME_SCENE_IDS.contains(this.getSceneId())) {
|
||||||
this.setSceneId(this.prevScene <= 0 ? 3 : this.prevScene); // if the player in home, make the player go back.
|
this.setSceneId(this.prevScene <= 0 ? 3 : this.prevScene); // if the player in home, make the player go back.
|
||||||
@ -1510,6 +1531,31 @@ public class Player implements PlayerHook, FieldFetch {
|
|||||||
getServer().getPlayers().values().removeIf(player1 -> player1 == this);
|
getServer().getPlayers().values().removeIf(player1 -> player1 == this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void unfreezeUnlockedScenePoints(int sceneId) {
|
||||||
|
// Unfreeze previously unlocked scene points. For example,
|
||||||
|
// the first weapon mats domain needs some script interaction
|
||||||
|
// to unlock. It needs to be unfrozen when GetScenePointReq
|
||||||
|
// comes in to be interactable again.
|
||||||
|
GameData.getScenePointEntryMap().values().stream()
|
||||||
|
.filter(scenePointEntry ->
|
||||||
|
// Note: Only DungeonEntry scene points need to be unfrozen
|
||||||
|
scenePointEntry.getPointData().getType().equals("DungeonEntry")
|
||||||
|
// groupLimit says this scene point needs to be unfrozen
|
||||||
|
&& scenePointEntry.getPointData().isGroupLimit())
|
||||||
|
.forEach(scenePointEntry -> {
|
||||||
|
// If this is a previously unlocked scene point,
|
||||||
|
// send unfreeze packet.
|
||||||
|
val pointId = scenePointEntry.getPointData().getId();
|
||||||
|
if (unlockedScenePoints.get(sceneId).contains(pointId)) {
|
||||||
|
this.sendPacket(new PacketUnfreezeGroupLimitNotify(pointId, sceneId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unfreezeUnlockedScenePoints() {
|
||||||
|
unlockedScenePoints.keySet().forEach(sceneId -> unfreezeUnlockedScenePoints(sceneId));
|
||||||
|
}
|
||||||
|
|
||||||
public int getLegendaryKey() {
|
public int getLegendaryKey() {
|
||||||
return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
|
return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import emu.grasscutter.game.quest.enums.*;
|
|||||||
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -32,12 +33,9 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
|
|||||||
|
|
||||||
public static final Set<Integer> IGNORED_OPEN_STATES =
|
public static final Set<Integer> IGNORED_OPEN_STATES =
|
||||||
Set.of(
|
Set.of(
|
||||||
1404, // OPEN_STATE_MENGDE_INFUSEDCRYSTAL, causes quest 'Mine Craft' to be given to the
|
1404 // OPEN_STATE_MENGDE_INFUSEDCRYSTAL, causes quest 'Mine Craft' to be given to the
|
||||||
// player at the start of the game.
|
// player at the start of the game.
|
||||||
// This should be removed when city reputation is implemented.
|
// This should be removed when city reputation is implemented.
|
||||||
57 // OPEN_STATE_PERSONAL_LINE, causes the prompt for showing character hangout quests to
|
|
||||||
// be permanently shown.
|
|
||||||
// This should be removed when character story quests are implemented.
|
|
||||||
);
|
);
|
||||||
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
|
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
|
||||||
// `map`.
|
// `map`.
|
||||||
@ -313,4 +311,28 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
|
|||||||
player.save();
|
player.save();
|
||||||
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
|
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************************************************
|
||||||
|
******************************************************************************************************************
|
||||||
|
* SCENETAGS
|
||||||
|
******************************************************************************************************************
|
||||||
|
*****************************************************************************************************************/
|
||||||
|
public void addSceneTag(int sceneId, int sceneTagId) {
|
||||||
|
player.getSceneTags().computeIfAbsent(sceneId, k -> new HashSet<>()).add(sceneTagId);
|
||||||
|
player.sendPacket(new PacketPlayerWorldSceneInfoListNotify(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delSceneTag(int sceneId, int sceneTagId) {
|
||||||
|
// Sanity check
|
||||||
|
if (player.getSceneTags().get(sceneId) == null) {
|
||||||
|
// Can't delete something that doesn't exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.getSceneTags().get(sceneId).remove(sceneTagId);
|
||||||
|
player.sendPacket(new PacketPlayerWorldSceneInfoListNotify(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkSceneTag(int sceneId, int sceneTagId) {
|
||||||
|
return player.getSceneTags().get(sceneId).contains(sceneTagId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,6 +425,30 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
this.getPlayer().sendPacket(responsePacket);
|
this.getPlayer().sendPacket(responsePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure new selected character index is alive.
|
||||||
|
// If not, change to another alive one or revive.
|
||||||
|
checkCurrentAvatarIsAlive(currentEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkCurrentAvatarIsAlive(EntityAvatar currentEntity) {
|
||||||
|
if (currentEntity == null) {
|
||||||
|
currentEntity = this.getCurrentAvatarEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure currently selected character is still alive
|
||||||
|
if (!this.getActiveTeam().get(this.currentCharacterIndex).isAlive()) {
|
||||||
|
// Character died in a dungeon challenge...
|
||||||
|
int replaceIndex = getDeadAvatarReplacement();
|
||||||
|
if (0 <= replaceIndex && replaceIndex < this.getActiveTeam().size()) {
|
||||||
|
this.currentCharacterIndex = replaceIndex;
|
||||||
|
} else {
|
||||||
|
// Team wiped in dungeon...
|
||||||
|
// Revive and change to first avatar.
|
||||||
|
this.currentCharacterIndex = 0;
|
||||||
|
this.reviveAvatar(this.getCurrentAvatarEntity().getAvatar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if character changed
|
// Check if character changed
|
||||||
var newAvatarEntity = this.getCurrentAvatarEntity();
|
var newAvatarEntity = this.getCurrentAvatarEntity();
|
||||||
if (currentEntity != null && newAvatarEntity != null && currentEntity != newAvatarEntity) {
|
if (currentEntity != null && newAvatarEntity != null && currentEntity != newAvatarEntity) {
|
||||||
@ -700,15 +724,16 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
this.updateTeamEntities(null);
|
this.updateTeamEntities(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanTemporaryTeam() {
|
public boolean cleanTemporaryTeam() {
|
||||||
// check if using temporary team
|
// check if using temporary team
|
||||||
if (useTemporarilyTeamIndex < 0) {
|
if (useTemporarilyTeamIndex < 0) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.useTemporarilyTeamIndex = -1;
|
this.useTemporarilyTeamIndex = -1;
|
||||||
this.temporaryTeam = null;
|
this.temporaryTeam = null;
|
||||||
this.updateTeamEntities(null);
|
this.updateTeamEntities(null);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setCurrentTeam(int teamId) {
|
public synchronized void setCurrentTeam(int teamId) {
|
||||||
@ -798,10 +823,7 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
|
|
||||||
public void onAvatarDie(long dieGuid) {
|
public void onAvatarDie(long dieGuid) {
|
||||||
EntityAvatar deadAvatar = this.getCurrentAvatarEntity();
|
EntityAvatar deadAvatar = this.getCurrentAvatarEntity();
|
||||||
|
if (deadAvatar == null || deadAvatar.getId() != dieGuid) return;
|
||||||
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerDieType dieType = deadAvatar.getKilledType();
|
PlayerDieType dieType = deadAvatar.getKilledType();
|
||||||
int killedBy = deadAvatar.getKilledBy();
|
int killedBy = deadAvatar.getKilledBy();
|
||||||
@ -813,20 +835,13 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
// TODO: Perhaps find a way to get vanilla experience?
|
// TODO: Perhaps find a way to get vanilla experience?
|
||||||
this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
||||||
} else {
|
} else {
|
||||||
// Replacement avatar
|
// Find replacement avatar
|
||||||
EntityAvatar replacement = null;
|
int replaceIndex = getDeadAvatarReplacement();
|
||||||
int replaceIndex = -1;
|
if (0 <= replaceIndex && replaceIndex < this.getActiveTeam().size()) {
|
||||||
|
// Set index and spawn replacement member
|
||||||
for (int i = 0; i < this.getActiveTeam().size(); i++) {
|
this.setCurrentCharacterIndex(replaceIndex);
|
||||||
EntityAvatar entity = this.getActiveTeam().get(i);
|
this.getPlayer().getScene().addEntity(this.getActiveTeam().get(replaceIndex));
|
||||||
if (entity.isAlive()) {
|
} else {
|
||||||
replaceIndex = i;
|
|
||||||
replacement = entity;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replacement == null) {
|
|
||||||
// No more living team members...
|
// No more living team members...
|
||||||
this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
||||||
// Invoke player team death event.
|
// Invoke player team death event.
|
||||||
@ -834,10 +849,6 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
new PlayerTeamDeathEvent(
|
new PlayerTeamDeathEvent(
|
||||||
this.getPlayer(), this.getActiveTeam().get(this.getCurrentCharacterIndex()));
|
this.getPlayer(), this.getActiveTeam().get(this.getCurrentCharacterIndex()));
|
||||||
event.call();
|
event.call();
|
||||||
} else {
|
|
||||||
// Set index and spawn replacement member
|
|
||||||
this.setCurrentCharacterIndex(replaceIndex);
|
|
||||||
this.getPlayer().getScene().addEntity(replacement);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,6 +856,20 @@ public final class TeamManager extends BasePlayerDataManager {
|
|||||||
this.getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0));
|
this.getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDeadAvatarReplacement() {
|
||||||
|
int replaceIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < this.getActiveTeam().size(); i++) {
|
||||||
|
EntityAvatar entity = this.getActiveTeam().get(i);
|
||||||
|
if (entity.isAlive()) {
|
||||||
|
replaceIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replaceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean reviveAvatar(Avatar avatar) {
|
public boolean reviveAvatar(Avatar avatar) {
|
||||||
for (EntityAvatar entity : this.getActiveTeam()) {
|
for (EntityAvatar entity : this.getActiveTeam()) {
|
||||||
if (entity.getAvatar() == avatar) {
|
if (entity.getAvatar() == avatar) {
|
||||||
|
@ -177,7 +177,8 @@ public enum ActionReason {
|
|||||||
ChannellerSlabLoopDungeonFirstPassReward(1090),
|
ChannellerSlabLoopDungeonFirstPassReward(1090),
|
||||||
ChannellerSlabLoopDungeonScoreReward(1091),
|
ChannellerSlabLoopDungeonScoreReward(1091),
|
||||||
HomeLimitedShopBuy(1092),
|
HomeLimitedShopBuy(1092),
|
||||||
HomeCoinCollect(1093);
|
HomeCoinCollect(1093),
|
||||||
|
HomeAvatarEventReward(1100);
|
||||||
|
|
||||||
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Map<String, ActionReason> stringMap = new HashMap<>();
|
private static final Map<String, ActionReason> stringMap = new HashMap<>();
|
||||||
|
@ -16,11 +16,10 @@ import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
|
|||||||
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
|
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import lombok.*;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.*;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Entity(value = "quests", useDiscriminator = false)
|
@Entity(value = "quests", useDiscriminator = false)
|
||||||
public class GameMainQuest {
|
public class GameMainQuest {
|
||||||
@ -170,23 +169,6 @@ public class GameMainQuest {
|
|||||||
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
|
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* We also need to check for unfinished childQuests in this MainQuest
|
|
||||||
* force them to complete and send a packet about this to the user,
|
|
||||||
* because at some points there are special "invisible" child quests that control
|
|
||||||
* some situations.
|
|
||||||
*
|
|
||||||
* For example, subQuest 35312 is responsible for the event of leaving the territory
|
|
||||||
* of the island with a statue and automatically returns the character back,
|
|
||||||
* quest 35311 completes the main quest line 353 and starts 35501 from
|
|
||||||
* new MainQuest 355 but if 35312 is not completed after the completion
|
|
||||||
* of the main quest 353 - the character will not be able to leave place
|
|
||||||
* (return again and again)
|
|
||||||
*/
|
|
||||||
// this.getChildQuests().values().stream()
|
|
||||||
// .filter(p -> p.state != QuestState.QUEST_STATE_FINISHED)
|
|
||||||
// .forEach(GameQuest::finish);
|
|
||||||
|
|
||||||
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
|
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
|
||||||
this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
|
this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
|
||||||
|
|
||||||
@ -329,11 +311,12 @@ public class GameMainQuest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the quest has a teleport position. Returns true if it does and adds the target position
|
* Checks if the quest has a teleport position. Returns true if it does and adds the target
|
||||||
* and rotation to the list.
|
* position and rotation to the list.
|
||||||
*
|
*
|
||||||
* @param subId The sub-quest ID.
|
* @param subId The sub-quest ID.
|
||||||
* @param posAndRot A list which will contain the position and rotation if the quest has a teleport.
|
* @param posAndRot A list which will contain the position and rotation if the quest has a
|
||||||
|
* teleport.
|
||||||
* @return True if the quest has a teleport position. False otherwise.
|
* @return True if the quest has a teleport position. False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean hasTeleportPosition(int subId, List<Position> posAndRot) {
|
public boolean hasTeleportPosition(int subId, List<Position> posAndRot) {
|
||||||
@ -383,46 +366,6 @@ public class GameMainQuest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryAcceptSubQuests(QuestCond condType, String paramStr, int... params) {
|
|
||||||
try {
|
|
||||||
List<GameQuest> subQuestsWithCond =
|
|
||||||
getChildQuests().values().stream()
|
|
||||||
.filter(
|
|
||||||
p ->
|
|
||||||
p.getState() == QuestState.QUEST_STATE_UNSTARTED
|
|
||||||
|| p.getState() == QuestState.UNFINISHED)
|
|
||||||
.filter(
|
|
||||||
p ->
|
|
||||||
p.getQuestData().getAcceptCond().stream()
|
|
||||||
.anyMatch(
|
|
||||||
q ->
|
|
||||||
condType == QuestCond.QUEST_COND_NONE || q.getType() == condType))
|
|
||||||
.toList();
|
|
||||||
var questSystem = owner.getServer().getQuestSystem();
|
|
||||||
|
|
||||||
for (GameQuest subQuestWithCond : subQuestsWithCond) {
|
|
||||||
var acceptCond = subQuestWithCond.getQuestData().getAcceptCond();
|
|
||||||
int[] accept = new int[acceptCond.size()];
|
|
||||||
|
|
||||||
for (int i = 0; i < subQuestWithCond.getQuestData().getAcceptCond().size(); i++) {
|
|
||||||
var condition = acceptCond.get(i);
|
|
||||||
boolean result =
|
|
||||||
questSystem.triggerCondition(
|
|
||||||
getOwner(), subQuestWithCond.getQuestData(), condition, paramStr, params);
|
|
||||||
accept[i] = result ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean shouldAccept =
|
|
||||||
LogicType.calculate(subQuestWithCond.getQuestData().getAcceptCondComb(), accept);
|
|
||||||
|
|
||||||
if (shouldAccept) subQuestWithCond.start();
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryFailSubQuests(QuestContent condType, String paramStr, int... params) {
|
public void tryFailSubQuests(QuestContent condType, String paramStr, int... params) {
|
||||||
try {
|
try {
|
||||||
List<GameQuest> subQuestsWithCond =
|
List<GameQuest> subQuestsWithCond =
|
||||||
@ -437,7 +380,7 @@ public class GameMainQuest {
|
|||||||
for (GameQuest subQuestWithCond : subQuestsWithCond) {
|
for (GameQuest subQuestWithCond : subQuestsWithCond) {
|
||||||
val failCond = subQuestWithCond.getQuestData().getFailCond();
|
val failCond = subQuestWithCond.getQuestData().getFailCond();
|
||||||
|
|
||||||
for (int i = 0; i < subQuestWithCond.getQuestData().getFailCond().size(); i++) {
|
for (int i = 0; i < failCond.size(); i++) {
|
||||||
val condition = failCond.get(i);
|
val condition = failCond.get(i);
|
||||||
if (condition.getType() == condType) {
|
if (condition.getType() == condType) {
|
||||||
boolean result =
|
boolean result =
|
||||||
@ -445,7 +388,7 @@ public class GameMainQuest {
|
|||||||
.getServer()
|
.getServer()
|
||||||
.getQuestSystem()
|
.getQuestSystem()
|
||||||
.triggerContent(subQuestWithCond, condition, paramStr, params);
|
.triggerContent(subQuestWithCond, condition, paramStr, params);
|
||||||
subQuestWithCond.getFailProgressList()[i] = result ? 1 : 0;
|
subQuestWithCond.setFailProgress(i, result ? 1 : 0);
|
||||||
if (result) {
|
if (result) {
|
||||||
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
|
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,13 @@ public class GameQuest {
|
|||||||
public boolean clearProgress(boolean notifyDelete) {
|
public boolean clearProgress(boolean notifyDelete) {
|
||||||
// TODO improve
|
// TODO improve
|
||||||
var oldState = state;
|
var oldState = state;
|
||||||
|
if (questData.getAcceptCond() != null && questData.getAcceptCond().size() != 0) {
|
||||||
|
this.getMainQuest()
|
||||||
|
.getQuestManager()
|
||||||
|
.getAcceptProgressLists()
|
||||||
|
.put(this.getSubQuestId(), new int[questData.getAcceptCond().size()]);
|
||||||
|
}
|
||||||
|
|
||||||
if (questData.getFinishCond() != null && questData.getFinishCond().size() != 0) {
|
if (questData.getFinishCond() != null && questData.getFinishCond().size() != 0) {
|
||||||
for (var condition : questData.getFinishCond()) {
|
for (var condition : questData.getFinishCond()) {
|
||||||
if (condition.getType() == QuestContent.QUEST_CONTENT_LUA_NOTIFY) {
|
if (condition.getType() == QuestContent.QUEST_CONTENT_LUA_NOTIFY) {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package emu.grasscutter.game.quest;
|
package emu.grasscutter.game.quest;
|
||||||
|
|
||||||
|
import static emu.grasscutter.GameConstants.DEBUG;
|
||||||
|
import static emu.grasscutter.config.Configuration.*;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.binout.*;
|
import emu.grasscutter.data.binout.*;
|
||||||
@ -12,21 +15,18 @@ import emu.grasscutter.net.proto.GivingRecordOuterClass.GivingRecord;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import static emu.grasscutter.GameConstants.DEBUG;
|
import lombok.*;
|
||||||
import static emu.grasscutter.config.Configuration.*;
|
|
||||||
|
|
||||||
public final class QuestManager extends BasePlayerManager {
|
public final class QuestManager extends BasePlayerManager {
|
||||||
@Getter private final Player player;
|
@Getter private final Player player;
|
||||||
|
|
||||||
@Getter private final Int2ObjectMap<GameMainQuest> mainQuests;
|
@Getter private final Int2ObjectMap<GameMainQuest> mainQuests;
|
||||||
|
@Getter private Int2ObjectMap<int[]> acceptProgressLists;
|
||||||
@Getter private final List<Integer> loggedQuests;
|
@Getter private final List<Integer> loggedQuests;
|
||||||
|
|
||||||
private long lastHourCheck = 0;
|
private long lastHourCheck = 0;
|
||||||
@ -53,6 +53,7 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
this.player = player;
|
this.player = player;
|
||||||
this.mainQuests = new Int2ObjectOpenHashMap<>();
|
this.mainQuests = new Int2ObjectOpenHashMap<>();
|
||||||
this.loggedQuests = new ArrayList<>();
|
this.loggedQuests = new ArrayList<>();
|
||||||
|
this.acceptProgressLists = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
this.loggedQuests.addAll(
|
this.loggedQuests.addAll(
|
||||||
@ -100,22 +101,17 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
* Attempts to add the giving action.
|
* Attempts to add the giving action.
|
||||||
*
|
*
|
||||||
* @param givingId The giving action ID.
|
* @param givingId The giving action ID.
|
||||||
* @throws IllegalStateException If the giving action is already active.
|
|
||||||
*/
|
*/
|
||||||
public void addGiveItemAction(int givingId) throws IllegalStateException {
|
public void addGiveItemAction(int givingId) throws IllegalStateException {
|
||||||
var progress = this.player.getPlayerProgress();
|
var progress = this.player.getPlayerProgress();
|
||||||
var givings = progress.getItemGivings();
|
var givings = progress.getItemGivings();
|
||||||
|
|
||||||
// Check if the action is already present.
|
// Check if the action is not present.
|
||||||
if (givings.containsKey(givingId)) {
|
if (!givings.containsKey(givingId)) {
|
||||||
throw new IllegalStateException("Giving action " + givingId + " is already active.");
|
givings.put(givingId, ItemGiveRecord.resolve(givingId));
|
||||||
|
player.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the action.
|
|
||||||
givings.put(givingId, ItemGiveRecord.resolve(givingId));
|
|
||||||
// Save the givings.
|
|
||||||
player.save();
|
|
||||||
|
|
||||||
this.sendGivingRecords();
|
this.sendGivingRecords();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,14 +221,11 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
this.player.sendPacket(new PacketGivingRecordNotify(this.getGivingRecords()));
|
this.player.sendPacket(new PacketGivingRecordNotify(this.getGivingRecords()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPlayerBorn() {
|
public void onLogin() {
|
||||||
if (this.isQuestingEnabled()) {
|
if (this.isQuestingEnabled()) {
|
||||||
this.enableQuests();
|
this.enableQuests();
|
||||||
this.sendGivingRecords();
|
this.sendGivingRecords();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void onLogin() {
|
|
||||||
|
|
||||||
List<GameMainQuest> activeQuests = getActiveMainQuests();
|
List<GameMainQuest> activeQuests = getActiveMainQuests();
|
||||||
List<GameQuest> activeSubs = new ArrayList<>(activeQuests.size());
|
List<GameQuest> activeSubs = new ArrayList<>(activeQuests.size());
|
||||||
@ -302,6 +295,17 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void enableQuests() {
|
public void enableQuests() {
|
||||||
|
GameData.getBeginCondQuestMap()
|
||||||
|
.keySet()
|
||||||
|
.forEach(
|
||||||
|
x -> {
|
||||||
|
if (x.contains("QUEST_COND_STATE_NOT_EQUAL"))
|
||||||
|
this.triggerEvent(
|
||||||
|
QuestCond.QUEST_COND_STATE_NOT_EQUAL, null, Integer.parseInt(x.substring(26)));
|
||||||
|
if (x.contains("QUEST_COND_STATE_EQUAL"))
|
||||||
|
this.triggerEvent(
|
||||||
|
QuestCond.QUEST_COND_STATE_EQUAL, null, Integer.parseInt(x.substring(22)));
|
||||||
|
});
|
||||||
this.triggerEvent(QuestCond.QUEST_COND_NONE, null, 0);
|
this.triggerEvent(QuestCond.QUEST_COND_NONE, null, 0);
|
||||||
this.triggerEvent(QuestCond.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER, null, 1);
|
this.triggerEvent(QuestCond.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER, null, 1);
|
||||||
}
|
}
|
||||||
@ -485,8 +489,6 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
eventExecutor.submit(() -> triggerEvent(condType, paramStr, params));
|
eventExecutor.submit(() -> triggerEvent(condType, paramStr, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
// QUEST_EXEC are handled directly by each subQuest
|
|
||||||
|
|
||||||
public void triggerEvent(QuestCond condType, String paramStr, int... params) {
|
public void triggerEvent(QuestCond condType, String paramStr, int... params) {
|
||||||
Grasscutter.getLogger().trace("Trigger Event {}, {}, {}", condType, paramStr, params);
|
Grasscutter.getLogger().trace("Trigger Event {}, {}, {}", condType, paramStr, params);
|
||||||
var potentialQuests = GameData.getQuestDataByConditions(condType, params[0], paramStr);
|
var potentialQuests = GameData.getQuestDataByConditions(condType, params[0], paramStr);
|
||||||
@ -503,15 +505,19 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
val acceptCond = questData.getAcceptCond();
|
val acceptCond = questData.getAcceptCond();
|
||||||
int[] accept = new int[acceptCond.size()];
|
acceptProgressLists.putIfAbsent(questData.getId(), new int[acceptCond.size()]);
|
||||||
for (int i = 0; i < acceptCond.size(); i++) {
|
for (int i = 0; i < acceptCond.size(); i++) {
|
||||||
val condition = acceptCond.get(i);
|
val condition = acceptCond.get(i);
|
||||||
boolean result =
|
if (condition.getType() == condType) {
|
||||||
questSystem.triggerCondition(owner, questData, condition, paramStr, params);
|
boolean result =
|
||||||
accept[i] = result ? 1 : 0;
|
questSystem.triggerCondition(owner, questData, condition, paramStr, params);
|
||||||
|
acceptProgressLists.get(questData.getId())[i] = result ? 1 : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept);
|
boolean shouldAccept =
|
||||||
|
LogicType.calculate(
|
||||||
|
questData.getAcceptCondComb(), acceptProgressLists.get(questData.getId()));
|
||||||
if (this.loggedQuests.contains(questData.getId())) {
|
if (this.loggedQuests.contains(questData.getId())) {
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.debug(
|
.debug(
|
||||||
@ -523,7 +529,7 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
Arrays.stream(params)
|
Arrays.stream(params)
|
||||||
.mapToObj(String::valueOf)
|
.mapToObj(String::valueOf)
|
||||||
.collect(Collectors.joining(", ")));
|
.collect(Collectors.joining(", ")));
|
||||||
for (var i = 0; i < accept.length; i++) {
|
for (var i = 0; i < acceptCond.size(); i++) {
|
||||||
var condition = acceptCond.get(i);
|
var condition = acceptCond.get(i);
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.debug(
|
.debug(
|
||||||
@ -533,14 +539,13 @@ public final class QuestManager extends BasePlayerManager {
|
|||||||
.filter(value -> value > 0)
|
.filter(value -> value > 0)
|
||||||
.mapToObj(String::valueOf)
|
.mapToObj(String::valueOf)
|
||||||
.collect(Collectors.joining(", ")),
|
.collect(Collectors.joining(", ")),
|
||||||
accept[i] == 1 ? "success" : "failure");
|
acceptProgressLists.get(questData.getId())[i] == 1 ? "success" : "failure");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldAccept) {
|
if (shouldAccept) {
|
||||||
GameQuest quest = owner.getQuestManager().addQuest(questData);
|
GameQuest quest = owner.getQuestManager().addQuest(questData);
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger().debug("Added quest {}", questData.getSubId());
|
||||||
.debug("Added quest {} result {}", questData.getSubId(), quest != null);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package emu.grasscutter.game.quest.conditions;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.excels.quest.QuestData;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.game.quest.QuestValueCond;
|
||||||
|
import emu.grasscutter.game.quest.enums.QuestCond;
|
||||||
|
|
||||||
|
@QuestValueCond(QuestCond.QUEST_COND_ITEM_GIVING_FINISHED)
|
||||||
|
public class ConditionItemGivingFinished extends BaseCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(
|
||||||
|
Player owner,
|
||||||
|
QuestData questData,
|
||||||
|
QuestData.QuestAcceptCondition condition,
|
||||||
|
String paramStr,
|
||||||
|
int... params) {
|
||||||
|
return condition.getParam()[0] == params[0]
|
||||||
|
&& (condition.getParam()[1] == 0 || condition.getParam()[1] == params[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package emu.grasscutter.game.quest.conditions;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.excels.quest.QuestData;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.game.quest.QuestValueCond;
|
||||||
|
import emu.grasscutter.game.quest.enums.QuestCond;
|
||||||
|
|
||||||
|
@QuestValueCond(QuestCond.QUEST_COND_MAIN_COOP_START)
|
||||||
|
public class ConditionMainCoopStart extends BaseCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(
|
||||||
|
Player owner,
|
||||||
|
QuestData questData,
|
||||||
|
QuestData.QuestAcceptCondition condition,
|
||||||
|
String paramStr,
|
||||||
|
int... params) {
|
||||||
|
return condition.getParam()[0] == params[0]
|
||||||
|
&& (condition.getParam()[1] == 0 || condition.getParam()[1] == params[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,9 @@ public class ConditionStateEqual extends BaseCondition {
|
|||||||
var questStateValue = condition.getParam()[1];
|
var questStateValue = condition.getParam()[1];
|
||||||
var checkQuest = owner.getQuestManager().getQuestById(questId);
|
var checkQuest = owner.getQuestManager().getQuestById(questId);
|
||||||
|
|
||||||
return checkQuest != null && checkQuest.getState().getValue() == questStateValue;
|
if (checkQuest == null) {
|
||||||
|
return questStateValue == 0;
|
||||||
|
}
|
||||||
|
return checkQuest.getState().getValue() == questStateValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,9 @@ public class ConditionStateNotEqual extends BaseCondition {
|
|||||||
var questStateValue = condition.getParam()[1];
|
var questStateValue = condition.getParam()[1];
|
||||||
var checkQuest = owner.getQuestManager().getQuestById(questId);
|
var checkQuest = owner.getQuestManager().getQuestById(questId);
|
||||||
|
|
||||||
return checkQuest != null && checkQuest.getState().getValue() != questStateValue;
|
if (checkQuest == null) {
|
||||||
|
return questStateValue != 0;
|
||||||
|
}
|
||||||
|
return checkQuest.getState().getValue() != questStateValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@ import emu.grasscutter.game.quest.*;
|
|||||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||||
|
|
||||||
@QuestValueContent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING)
|
@QuestValueContent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING)
|
||||||
public final class ContentFinishGivingItem extends BaseContent {
|
public final class ContentFinishItemGiving extends BaseContent {
|
||||||
@Override
|
@Override
|
||||||
public boolean execute(
|
public boolean execute(
|
||||||
GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
|
GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
|
||||||
return condition.getParam()[0] == params[0] && condition.getParam()[1] == params[1];
|
return condition.getParam()[0] == params[0]
|
||||||
|
&& (condition.getParam()[1] == 0 || condition.getParam()[1] == params[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,7 +25,7 @@ public enum QuestCond implements QuestTrigger {
|
|||||||
QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER(17),
|
QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER(17),
|
||||||
QUEST_COND_SCENE_AREA_UNLOCKED(18), // missing, only NPC groups/talks
|
QUEST_COND_SCENE_AREA_UNLOCKED(18), // missing, only NPC groups/talks
|
||||||
QUEST_COND_ITEM_GIVING_ACTIVED(19), // missing
|
QUEST_COND_ITEM_GIVING_ACTIVED(19), // missing
|
||||||
QUEST_COND_ITEM_GIVING_FINISHED(20), // missing
|
QUEST_COND_ITEM_GIVING_FINISHED(20),
|
||||||
QUEST_COND_IS_DAYTIME(21), // only NPC groups
|
QUEST_COND_IS_DAYTIME(21), // only NPC groups
|
||||||
QUEST_COND_CURRENT_AVATAR(22), // missing
|
QUEST_COND_CURRENT_AVATAR(22), // missing
|
||||||
QUEST_COND_CURRENT_AREA(23), // missing
|
QUEST_COND_CURRENT_AREA(23), // missing
|
||||||
@ -54,7 +54,7 @@ public enum QuestCond implements QuestTrigger {
|
|||||||
QUEST_COND_QUEST_GLOBAL_VAR_LESS(46),
|
QUEST_COND_QUEST_GLOBAL_VAR_LESS(46),
|
||||||
QUEST_COND_PERSONAL_LINE_UNLOCK(47),
|
QUEST_COND_PERSONAL_LINE_UNLOCK(47),
|
||||||
QUEST_COND_CITY_REPUTATION_REQUEST(48), // missing
|
QUEST_COND_CITY_REPUTATION_REQUEST(48), // missing
|
||||||
QUEST_COND_MAIN_COOP_START(49), // missing
|
QUEST_COND_MAIN_COOP_START(49),
|
||||||
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT(50), // missing
|
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT(50), // missing
|
||||||
QUEST_COND_CITY_REPUTATION_LEVEL(51), // missing, only NPC groups
|
QUEST_COND_CITY_REPUTATION_LEVEL(51), // missing, only NPC groups
|
||||||
QUEST_COND_CITY_REPUTATION_UNLOCK(52), // missing, currently unused
|
QUEST_COND_CITY_REPUTATION_UNLOCK(52), // missing, currently unused
|
||||||
|
@ -25,6 +25,10 @@ public class TowerLevelRecord {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getLevelStars(int levelId) {
|
||||||
|
return passedLevelMap.get(levelId);
|
||||||
|
}
|
||||||
|
|
||||||
public int getStarCount() {
|
public int getStarCount() {
|
||||||
return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum();
|
return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum();
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
package emu.grasscutter.game.tower;
|
package emu.grasscutter.game.tower;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.excels.tower.TowerLevelData;
|
import emu.grasscutter.data.excels.tower.TowerLevelData;
|
||||||
import emu.grasscutter.game.dungeons.*;
|
import emu.grasscutter.game.dungeons.*;
|
||||||
import emu.grasscutter.game.player.*;
|
import emu.grasscutter.game.player.*;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
public class TowerManager extends BasePlayerManager {
|
public class TowerManager extends BasePlayerManager {
|
||||||
private static final List<DungeonSettleListener> towerDungeonSettleListener =
|
private static final List<DungeonSettleListener> towerDungeonSettleListener =
|
||||||
List.of(new TowerDungeonSettleListener());
|
List.of(new TowerDungeonSettleListener());
|
||||||
|
|
||||||
|
private int currentPossibleStars = 0;
|
||||||
|
@Getter private boolean inProgress;
|
||||||
|
@Getter private int currentTimeLimit;
|
||||||
|
|
||||||
public TowerManager(Player player) {
|
public TowerManager(Player player) {
|
||||||
super(player);
|
super(player);
|
||||||
}
|
}
|
||||||
@ -32,6 +38,32 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
return this.getTowerData().currentLevel + 1;
|
return this.getTowerData().currentLevel + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onTick() {
|
||||||
|
var challenge = player.getScene().getChallenge();
|
||||||
|
if (challenge == null || !challenge.inProgress()) return;
|
||||||
|
|
||||||
|
// Check star conditions and notify client if any failed.
|
||||||
|
int stars = getCurLevelStars();
|
||||||
|
while (stars < currentPossibleStars) {
|
||||||
|
player
|
||||||
|
.getSession()
|
||||||
|
.send(
|
||||||
|
new PacketTowerLevelStarCondNotify(
|
||||||
|
getTowerData().currentFloorId, getCurrentLevel(), currentPossibleStars));
|
||||||
|
currentPossibleStars--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBegin() {
|
||||||
|
var challenge = player.getScene().getChallenge();
|
||||||
|
inProgress = true;
|
||||||
|
currentTimeLimit = challenge.getTimeLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnd() {
|
||||||
|
inProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Integer, TowerLevelRecord> getRecordMap() {
|
public Map<Integer, TowerLevelRecord> getRecordMap() {
|
||||||
Map<Integer, TowerLevelRecord> recordMap = getTowerData().recordMap;
|
Map<Integer, TowerLevelRecord> recordMap = getTowerData().recordMap;
|
||||||
if (recordMap == null || recordMap.size() == 0) {
|
if (recordMap == null || recordMap.size() == 0) {
|
||||||
@ -84,9 +116,12 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
// stop using skill
|
// stop using skill
|
||||||
player.getSession().send(new PacketCanUseSkillNotify(false));
|
player.getSession().send(new PacketCanUseSkillNotify(false));
|
||||||
// notify the cond of stars
|
// notify the cond of stars
|
||||||
|
currentPossibleStars = 3;
|
||||||
player
|
player
|
||||||
.getSession()
|
.getSession()
|
||||||
.send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel()));
|
.send(
|
||||||
|
new PacketTowerLevelStarCondNotify(
|
||||||
|
getTowerData().currentFloorId, getCurrentLevel(), currentPossibleStars + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyCurLevelRecordChange() {
|
public void notifyCurLevelRecordChange() {
|
||||||
@ -97,6 +132,41 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
getTowerData().currentFloorId, getCurrentLevel()));
|
getTowerData().currentFloorId, getCurrentLevel()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCurLevelStars() {
|
||||||
|
var scene = player.getScene();
|
||||||
|
var challenge = scene.getChallenge();
|
||||||
|
if (challenge == null) {
|
||||||
|
Grasscutter.getLogger().error("getCurLevelStars: no challenge registered!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId());
|
||||||
|
// 0-based indexing. "star" = 0 means checking for 1-star conditions.
|
||||||
|
int star;
|
||||||
|
for (star = 2; star >= 0; star--) {
|
||||||
|
var cond = levelData.getCondType(star);
|
||||||
|
if (cond == TowerLevelData.TowerCondType.TOWER_COND_CHALLENGE_LEFT_TIME_MORE_THAN) {
|
||||||
|
var params = levelData.getTimeCond(star);
|
||||||
|
var timeRemaining =
|
||||||
|
challenge.getTimeLimit() - (scene.getSceneTimeSeconds() - challenge.getStartedAt());
|
||||||
|
if (timeRemaining >= params.getMinimumTimeInSeconds()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (cond == TowerLevelData.TowerCondType.TOWER_COND_LEFT_HP_GREATER_THAN) {
|
||||||
|
// TODO: Check monolith health
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.error(
|
||||||
|
"getCurLevelStars: Tower level {} has no or unknown condition defined for {} stars",
|
||||||
|
getCurrentLevelId(),
|
||||||
|
star + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return star + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public void notifyCurLevelRecordChangeWhenDone(int stars) {
|
public void notifyCurLevelRecordChangeWhenDone(int stars) {
|
||||||
Map<Integer, TowerLevelRecord> recordMap = this.getRecordMap();
|
Map<Integer, TowerLevelRecord> recordMap = this.getRecordMap();
|
||||||
int currentFloorId = getTowerData().currentFloorId;
|
int currentFloorId = getTowerData().currentFloorId;
|
||||||
@ -105,8 +175,16 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
currentFloorId,
|
currentFloorId,
|
||||||
new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(), stars));
|
new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(), stars));
|
||||||
} else {
|
} else {
|
||||||
recordMap.put(
|
// Only update record if better than previous
|
||||||
currentFloorId, recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(), stars));
|
var prevRecord = recordMap.get(currentFloorId);
|
||||||
|
var passedLevelMap = prevRecord.getPassedLevelMap();
|
||||||
|
int prevStars = 0;
|
||||||
|
if (passedLevelMap.containsKey(getCurrentLevelId())) {
|
||||||
|
prevStars = prevRecord.getLevelStars(getCurrentLevelId());
|
||||||
|
}
|
||||||
|
if (stars > prevStars) {
|
||||||
|
recordMap.put(currentFloorId, prevRecord.setLevelStars(getCurrentLevelId(), stars));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getTowerData().currentLevel++;
|
this.getTowerData().currentLevel++;
|
||||||
|
@ -5,6 +5,13 @@ import lombok.Data;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class GroupReplacementData {
|
public class GroupReplacementData {
|
||||||
int id;
|
public int id;
|
||||||
List<Integer> replace_groups;
|
public List<Integer> replace_groups;
|
||||||
|
|
||||||
|
public GroupReplacementData() {}
|
||||||
|
|
||||||
|
public GroupReplacementData(int id, List<Integer> replace_groups) {
|
||||||
|
this.id = id;
|
||||||
|
this.replace_groups = replace_groups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import emu.grasscutter.server.event.entity.EntityCreationEvent;
|
|||||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
|
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
|
||||||
import emu.grasscutter.utils.objects.KahnsSort;
|
import emu.grasscutter.utils.algorithms.KahnsSort;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -158,7 +158,7 @@ public class Scene {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameEntity getEntityByConfigId(int configId) {
|
public GameEntity getFirstEntityByConfigId(int configId) {
|
||||||
return this.entities.values().stream()
|
return this.entities.values().stream()
|
||||||
.filter(x -> x.getConfigId() == configId)
|
.filter(x -> x.getConfigId() == configId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
@ -535,7 +535,17 @@ public class Scene {
|
|||||||
"Can not solve monster drop: drop_id = {}, drop_tag = {}. Falling back to legacy drop system.",
|
"Can not solve monster drop: drop_id = {}, drop_tag = {}. Falling back to legacy drop system.",
|
||||||
monster.getMetaMonster().drop_id,
|
monster.getMetaMonster().drop_id,
|
||||||
monster.getMetaMonster().drop_tag);
|
monster.getMetaMonster().drop_tag);
|
||||||
getWorld().getServer().getDropSystemLegacy().callDrop(monster);
|
world.getServer().getDropSystemLegacy().callDrop(monster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof EntityGadget gadget) {
|
||||||
|
if (gadget.getMetaGadget() != null) {
|
||||||
|
world
|
||||||
|
.getServer()
|
||||||
|
.getDropSystem()
|
||||||
|
.handleChestDrop(
|
||||||
|
gadget.getMetaGadget().drop_id, gadget.getMetaGadget().drop_count, gadget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,6 +597,13 @@ public class Scene {
|
|||||||
|
|
||||||
blossomManager.onTick();
|
blossomManager.onTick();
|
||||||
|
|
||||||
|
// Should be OK to check only player 0,
|
||||||
|
// as no other players could enter Tower
|
||||||
|
var towerManager = getPlayers().get(0).getTowerManager();
|
||||||
|
if (towerManager != null) {
|
||||||
|
towerManager.onTick();
|
||||||
|
}
|
||||||
|
|
||||||
this.checkNpcGroup();
|
this.checkNpcGroup();
|
||||||
|
|
||||||
this.finishLoading();
|
this.finishLoading();
|
||||||
@ -806,8 +823,8 @@ public class Scene {
|
|||||||
|
|
||||||
int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride);
|
int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride);
|
||||||
|
|
||||||
EntityMonster monster = new EntityMonster(this, data, entry.getPos(), level);
|
EntityMonster monster =
|
||||||
monster.getRotation().set(entry.getRot());
|
new EntityMonster(this, data, entry.getPos(), entry.getRot(), level);
|
||||||
monster.setGroupId(entry.getGroup().getGroupId());
|
monster.setGroupId(entry.getGroup().getGroupId());
|
||||||
monster.setPoseId(entry.getPoseId());
|
monster.setPoseId(entry.getPoseId());
|
||||||
monster.setConfigId(entry.getConfigId());
|
monster.setConfigId(entry.getConfigId());
|
||||||
@ -1093,6 +1110,9 @@ public class Scene {
|
|||||||
if (group.regions != null) {
|
if (group.regions != null) {
|
||||||
group.regions.values().forEach(getScriptManager()::deregisterRegion);
|
group.regions.values().forEach(getScriptManager()::deregisterRegion);
|
||||||
}
|
}
|
||||||
|
if (challenge != null && group.id == challenge.getGroup().id) {
|
||||||
|
challenge.fail();
|
||||||
|
}
|
||||||
|
|
||||||
scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group);
|
scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group);
|
||||||
this.loadedGroups.remove(group);
|
this.loadedGroups.remove(group);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package emu.grasscutter.game.world;
|
package emu.grasscutter.game.world;
|
||||||
|
|
||||||
|
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
@ -17,14 +20,13 @@ import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
|
|||||||
import emu.grasscutter.server.game.GameServer;
|
import emu.grasscutter.server.game.GameServer;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.ConversionUtils;
|
import emu.grasscutter.utils.ConversionUtils;
|
||||||
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
|
|
||||||
|
|
||||||
public class World implements Iterable<Player> {
|
public class World implements Iterable<Player> {
|
||||||
@Getter private final GameServer server;
|
@Getter private final GameServer server;
|
||||||
@Getter private Player host;
|
@Getter private Player host;
|
||||||
@ -40,10 +42,20 @@ public class World implements Iterable<Player> {
|
|||||||
@Getter private boolean timeLocked;
|
@Getter private boolean timeLocked;
|
||||||
|
|
||||||
private long lastUpdateTime;
|
private long lastUpdateTime;
|
||||||
@Getter private int tickCount = 0;
|
@Getter protected int tickCount = 0;
|
||||||
@Getter private boolean isPaused = false;
|
@Getter private boolean isPaused = false;
|
||||||
@Getter private long currentWorldTime;
|
@Getter private long currentWorldTime;
|
||||||
|
|
||||||
|
private static final ExecutorService eventExecutor =
|
||||||
|
new ThreadPoolExecutor(
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
60,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingDeque<>(1000),
|
||||||
|
FastThreadLocalThread::new,
|
||||||
|
new ThreadPoolExecutor.AbortPolicy());
|
||||||
|
|
||||||
public World(Player player) {
|
public World(Player player) {
|
||||||
this(player, false);
|
this(player, false);
|
||||||
}
|
}
|
||||||
@ -73,6 +85,8 @@ public class World implements Iterable<Player> {
|
|||||||
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
|
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
|
||||||
this.entity = new EntityWorld(this);
|
this.entity = new EntityWorld(this);
|
||||||
this.lastUpdateTime = System.currentTimeMillis();
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
server.registerWorld(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelEntityId() {
|
public int getLevelEntityId() {
|
||||||
@ -310,6 +324,21 @@ public class World implements Iterable<Player> {
|
|||||||
this.getScenes().values().forEach(Scene::saveGroups);
|
this.getScenes().values().forEach(Scene::saveGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void queueTransferPlayerToScene(Player player, int sceneId, Position pos, int delayMs) {
|
||||||
|
player.setQueuedTeleport(
|
||||||
|
eventExecutor.submit(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
transferPlayerToScene(player, sceneId, pos);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.trace(
|
||||||
|
"queueTransferPlayerToScene: teleport to scene {} is interrupted", sceneId);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean transferPlayerToScene(Player player, int sceneId, Position pos) {
|
public boolean transferPlayerToScene(Player player, int sceneId, Position pos) {
|
||||||
return this.transferPlayerToScene(player, sceneId, TeleportType.INTERNAL, null, pos);
|
return this.transferPlayerToScene(player, sceneId, TeleportType.INTERNAL, null, pos);
|
||||||
}
|
}
|
||||||
@ -380,6 +409,16 @@ public class World implements Iterable<Player> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean transferPlayerToScene(Player player, TeleportProperties teleportProperties) {
|
public boolean transferPlayerToScene(Player player, TeleportProperties teleportProperties) {
|
||||||
|
// If a queued teleport already exists, cancel it. This prevents the player from
|
||||||
|
// becoming stranded in a dungeon due to quitting it by teleporting to a map waypoint.
|
||||||
|
synchronized (player) {
|
||||||
|
var queuedTeleport = player.getQueuedTeleport();
|
||||||
|
if (queuedTeleport != null) {
|
||||||
|
player.setQueuedTeleport(null);
|
||||||
|
queuedTeleport.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the teleport properties are valid.
|
// Check if the teleport properties are valid.
|
||||||
if (teleportProperties.getTeleportTo() == null)
|
if (teleportProperties.getTeleportTo() == null)
|
||||||
teleportProperties.setTeleportTo(player.getPosition());
|
teleportProperties.setTeleportTo(player.getPosition());
|
||||||
@ -397,37 +436,50 @@ public class World implements Iterable<Player> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene oldScene = null;
|
Scene oldScene = player.getScene();
|
||||||
if (player.getScene() != null) {
|
var newScene = this.getSceneById(teleportProperties.getSceneId());
|
||||||
oldScene = player.getScene();
|
|
||||||
|
|
||||||
|
// Move directly in the same scene.
|
||||||
|
if (newScene == oldScene && teleportProperties.getTeleportType() == TeleportType.COMMAND) {
|
||||||
|
// Set player position and rotation
|
||||||
|
if (teleportProperties.getTeleportTo() != null) {
|
||||||
|
player.getPosition().set(teleportProperties.getTeleportTo());
|
||||||
|
}
|
||||||
|
if (teleportProperties.getTeleportRot() != null) {
|
||||||
|
player.getRotation().set(teleportProperties.getTeleportRot());
|
||||||
|
}
|
||||||
|
player.sendPacket(new PacketSceneEntityAppearNotify(player));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldScene != null) {
|
||||||
// Don't deregister scenes if the player is going to tp back into them
|
// Don't deregister scenes if the player is going to tp back into them
|
||||||
if (oldScene.getId() == teleportProperties.getSceneId()) {
|
if (oldScene == newScene) {
|
||||||
oldScene.setDontDestroyWhenEmpty(true);
|
oldScene.setDontDestroyWhenEmpty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
oldScene.removePlayer(player);
|
oldScene.removePlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newScene = this.getSceneById(teleportProperties.getSceneId());
|
if (newScene != null) {
|
||||||
newScene.addPlayer(player);
|
newScene.addPlayer(player);
|
||||||
|
|
||||||
player.getTeamManager().applyAbilities(newScene);
|
player.getTeamManager().applyAbilities(newScene);
|
||||||
|
|
||||||
// Dungeon
|
// Dungeon
|
||||||
// Dungeon system is handling this already
|
// Dungeon system is handling this already
|
||||||
// if(dungeonData!=null){
|
// if(dungeonData!=null){
|
||||||
// var dungeonManager = new DungeonManager(newScene, dungeonData);
|
// var dungeonManager = new DungeonManager(newScene, dungeonData);
|
||||||
// dungeonManager.startDungeon();
|
// dungeonManager.startDungeon();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
SceneConfig config = newScene.getScriptManager().getConfig();
|
SceneConfig config = newScene.getScriptManager().getConfig();
|
||||||
if (teleportProperties.getTeleportTo() == null && config != null) {
|
if (teleportProperties.getTeleportTo() == null && config != null) {
|
||||||
if (config.born_pos != null) {
|
if (config.born_pos != null) {
|
||||||
teleportProperties.setTeleportTo(config.born_pos);
|
teleportProperties.setTeleportTo(config.born_pos);
|
||||||
}
|
}
|
||||||
if (config.born_rot != null) {
|
if (config.born_rot != null) {
|
||||||
teleportProperties.setTeleportRot(config.born_rot);
|
teleportProperties.setTeleportRot(config.born_rot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,8 +491,8 @@ public class World implements Iterable<Player> {
|
|||||||
player.getRotation().set(teleportProperties.getTeleportRot());
|
player.getRotation().set(teleportProperties.getTeleportRot());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldScene != null && newScene != oldScene) {
|
if (oldScene != null && newScene != null && newScene != oldScene) {
|
||||||
newScene.setPrevScene(oldScene.getId());
|
newScene.setPrevScenePoint(oldScene.getPrevScenePoint());
|
||||||
oldScene.setDontDestroyWhenEmpty(false);
|
oldScene.setDontDestroyWhenEmpty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package emu.grasscutter.net.packet;
|
|||||||
|
|
||||||
import com.google.protobuf.GeneratedMessageV3;
|
import com.google.protobuf.GeneratedMessageV3;
|
||||||
import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead;
|
import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead;
|
||||||
import emu.grasscutter.utils.Crypto;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class BasePacket {
|
public class BasePacket {
|
||||||
@ -108,13 +107,7 @@ public class BasePacket {
|
|||||||
this.writeBytes(baos, data);
|
this.writeBytes(baos, data);
|
||||||
this.writeUint16(baos, const2);
|
this.writeUint16(baos, const2);
|
||||||
|
|
||||||
byte[] packet = baos.toByteArray();
|
return 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) {
|
public void writeUint16(ByteArrayOutputStream baos, int i) {
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package emu.grasscutter.plugin;
|
package emu.grasscutter.plugin;
|
||||||
|
|
||||||
|
import static emu.grasscutter.utils.lang.Language.translate;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.server.event.*;
|
import emu.grasscutter.server.event.*;
|
||||||
import emu.grasscutter.utils.*;
|
import emu.grasscutter.utils.*;
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.jar.*;
|
import java.util.jar.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import static emu.grasscutter.utils.lang.Language.translate;
|
import lombok.*;
|
||||||
|
|
||||||
/** Manages the server's plugins and the event system. */
|
/** Manages the server's plugins and the event system. */
|
||||||
public final class PluginManager {
|
public final class PluginManager {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user