44 Commits

Author SHA1 Message Date
dad7821e26 Give thread pools time to terminate & Save all data before shutting down 2023-09-16 19:34:30 -04:00
539fa16160 Fix packet handling 2023-09-16 19:25:37 -04:00
3d2e0d0451 Merge branch 'development' into hyper-optimization 2023-09-16 19:01:43 -04:00
30f7580184 Merge remote-tracking branch 'origin/development' into development 2023-09-16 18:59:02 -04:00
65eaaa96f2 Apply changes from #2310 2023-09-16 18:58:36 -04:00
70a961e167 Format code [skip actions] 2023-09-16 22:44:16 +00:00
dd78addc29 Fix ability modifier serialization 2023-09-16 18:36:16 -04:00
43467ebc85 Factor fight properties into healing calculation 2023-09-16 18:32:01 -04:00
1f15d6219b Format code [skip actions] 2023-09-16 21:40:49 +00:00
fb0c2dbc84 Merge pull request #2341
* enableRandomEncryptSeed

* update config version

* Merge branch 'Grasscutters:development' into random-seed

* make the codes more beautiful

* move KahnsSort to algorithms

* rename variables

* Merge branch 'development' into random-seed
2023-09-16 14:38:39 -07:00
b8f7aea168 Format code [skip actions] 2023-09-16 21:33:36 +00:00
cf8092e8ba Update languages [skip actions] 2023-09-16 21:32:21 +00:00
0e44d18ae9 Better SceneTags (#2361)
* Add scene tag handling

* Move warns to debug

* Move to PlayerProgressManager

* Add success message

* Inline check

* Improve per-scene handling

* Update src/main/java/emu/grasscutter/command/commands/SetSceneTagCommand.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Update src/main/java/emu/grasscutter/command/commands/SetSceneTagCommand.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Fix types

* Small fix

* Update ScriptLib.java

---------

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>
2023-09-16 17:31:59 -04:00
5bd8f532c1 i forgot to add ` 2023-09-16 17:23:54 -04:00
ad62b6b11d Format code [skip actions] 2023-09-16 00:53:28 +00:00
c9a43a5e98 Format code [skip actions] 2023-09-16 00:52:37 +00:00
5458d36102 fix: HomeModuleManager isnt ticked unless player changes module (#2365) 2023-09-15 20:51:16 -04:00
c4dbb6851b Implement passJumpDungeon. Make dungeons kick you out to the correct scene (#2366) 2023-09-15 20:50:56 -04:00
2643c6b3b7 Format code [skip actions] 2023-09-15 00:45:41 +00:00
84e1371499 Fixes for alchemy dude's quest part 2 (#2362)
* Make platforms able to reset

platforms apparently are able to run again once reaching the last point. As well, canceling a timer seems to return a 0, not a 1

* Fix Seelies

Added HandlerClientScriptEventNotify (shoutouts to Hartie!) and cleaned up the platform stuff in ScriptLib.

There is a problem with HandlerClientScriptEventNotify where the client seems to only pass 0 into param1 instead of the configId. I coded in a workaround, but someone with greater access to things should check up on what is going on
2023-09-14 20:44:30 -04:00
f955bb1e16 Little update to item giving handling (#2363)
* Little update to HandlerItemGivingReq.java

GIVING_METHOD_GROUP is used in cooking girl's quest

* Send the giving packet at the start of relogs, even if encountered before.

* Make item checking not exact

You can have more items in your inventory than what you are submitting.
2023-09-14 20:44:08 -04:00
2b64814534 fix: home worlds are ticked twice (#2360) 2023-09-14 01:22:29 -04:00
ea5ee075a7 Merge remote-tracking branch 'origin/development' into development 2023-09-13 21:03:44 -04:00
6108a3bb37 Handbook documentation & build flow fixes 2023-09-13 21:03:32 -04:00
98a83b649e Add more precise instructions for the handbook (#2359)
* Add more precise instructions for the handbook

* Reformat Building section
2023-09-13 21:02:20 -04:00
a90a81c705 Add deferrable saving to GameItem 2023-09-13 00:34:17 -04:00
fb215e06cd Start an auto-save task which runs every 5 minutes
this should be configurable
2023-09-13 00:33:47 -04:00
4f62e484ad Update ru-RU.json (#2356)
translation & typo
2023-09-12 11:56:31 -04:00
5fd31aece8 Fix home nulls (#2355)
* Add null checking

* Sanity check for moduleManager
2023-09-12 01:07:17 -04:00
8de281d4da Format code [skip actions] 2023-09-10 23:21:44 +00:00
fbe2b138ee Fixes for alchemy dude's quest (#2352)
* Add drops for gadgets

Gadgets only have drop_id when they are not chests (chest_drop_id). When drop_id is not set (0), handleChestDrop quickly exits

* Implement QUEST_COND_ITEM_GIVING_FINISHED

Took the oppertunity to Rename ContentFinishGivingItem to ItemGiving

* Store accept conditions like fail and finish content are

Took the oppertunity to clean up some old code as well

conditions are stored in QuestManager

* Update src/main/java/emu/grasscutter/game/quest/QuestManager.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Update ConditionItemGivingFinished.java

---------

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>
2023-09-10 19:20:28 -04:00
23aff95a2e Use every thread available to the JVM, in a work stealing pool for packet handling 2023-09-10 12:26:38 -04:00
47c96fd964 Regular ScriptLib update! (#2349)
* Regular ScriptLib update!

* Move RefreshGroup down to debug

* Update SetGadgetEnableInteract
2023-09-09 20:49:22 -04:00
04370f1a21 Update CN README to game REL 4.0 (#2350) 2023-09-09 20:48:32 -04:00
3c3adea406 Add async bulk saving of objects 2023-09-09 20:41:53 -04:00
2efa022d83 Implement basic testing (login & HTTP status) 2023-09-09 18:10:44 -04:00
5b5ec9b6b4 Organize & sort the KCP system 2023-09-09 15:13:43 -04:00
7845c54570 Format code [skip actions] 2023-09-09 03:07:29 +00:00
818b638bed Added Hindi Language Support (#2347)
* Added Hindi Language Support 

Please review the code

* Update README_HE.md

* Update README_NL.md

* Update README_es-ES.md

* Update README_fil-PH.md

* Update README_fr-FR.md

* Update README_id-ID.md

* Update README_it-IT.md

* Update README_ja-JP.md

* Update README_ko-KR.md

* Update README_pl-PL.md

* Update README_ru-RU.md

* Update README_vi-VN.md

* Update README_zh-CN.md

* Update README_zh-TW.md
2023-09-08 23:07:18 -04:00
a9402f487f Implement RegionShape.POLYGON and RegionShape.CYLINDER (#2348)
Also took the opportunity to sort them in order and use multiplication instead of pow.
2023-09-08 23:06:14 -04:00
cdcdf924bd Format code [skip actions] 2023-09-08 03:35:28 +00:00
fc42f665a7 feat: implement teapot suite (#2344)
* feat: implement teapot suite

* fix: home animals, check respawn, etc

* fix: NPE and cancel summon events

* fix: forgot to send eventId also
2023-09-07 23:34:03 -04:00
83602f78ae Update readme to 4.0 from 3.7 (#2343) 2023-09-07 23:33:13 -04:00
8db1f597ce Format code [skip actions] 2023-09-02 23:51:35 +00:00
132 changed files with 2835 additions and 809 deletions

View File

@ -26,7 +26,7 @@
- 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 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.
- 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:**
- [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)
- [NodeJS](https://nodejs.org/en/download) (Optional, for building the handbook)
##### Windows
##### Clone
```shell
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
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
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
cd Grasscutter
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.

View File

@ -386,6 +386,12 @@ tasks.register('generateHandbook') {
return
}
// Install dependencies before building.
exec {
workingDir 'src/handbook'
commandLine npm, 'install'
}
// Build the handbook.
exec {
workingDir 'src/handbook'

View File

@ -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>
[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).

View File

@ -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>
[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.

View File

@ -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>
[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).

View File

@ -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>
[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).

View File

@ -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>
[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).

78
docs/README_hn-IN.md Normal file
View File

@ -0,0 +1,78 @@
![Grasscutter](https://socialify.git.ci/Grasscutters/Grasscutter/image?description=1&forks=1&issues=1&language=1&logo=https%3A%2F%2Fs2.loli.net%2F2022%2F04%2F25%2FxOiJn7lCdcT5Mw1.png&name=1&owner=1&pulls=1&stargazers=1&theme=Light)
<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) और सपोर्ट चैनल पर जाएं.

View File

@ -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>
[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.

View File

@ -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>
[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).

View File

@ -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>
[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)をよくお読みください。

View File

@ -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>
[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)을 주의 깊게 읽어주세요.

View File

@ -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>
[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).

View File

@ -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>
[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).

View File

@ -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>
[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 .

View File

@ -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>
[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)。
@ -26,7 +26,7 @@
- 获取Java 17https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
- 获取[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”为后缀的安装包
- 以管理员身份打开Culivation按右上角的下载按钮。

View File

@ -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>
[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)。

View File

@ -36,21 +36,21 @@ public final class HomePlantSubFieldDataOuterClass {
int getEntityIdList(int index);
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The enum numeric value on the wire for cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @return The enum numeric value on the wire for status.
*/
int getCAKDDMKAIMDValue();
int getStatusValue();
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @return The status.
*/
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getCAKDDMKAIMD();
emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getStatus();
/**
* <code>uint32 JHFNDBIHLNB = 8;</code>
* @return The jHFNDBIHLNB.
* <code>uint32 seed_id = 8;</code>
* @return The seedId.
*/
int getJHFNDBIHLNB();
int getSeedId();
/**
* <code>fixed32 end_time = 14;</code>
@ -59,10 +59,10 @@ public final class HomePlantSubFieldDataOuterClass {
int getEndTime();
/**
* <code>uint32 KHFGOPCOAGM = 3;</code>
* @return The kHFGOPCOAGM.
* <code>uint32 gather_point_type = 3;</code>
* @return The gatherPointType.
*/
int getKHFGOPCOAGM();
int getGatherPointType();
}
/**
* <pre>
@ -82,7 +82,7 @@ public final class HomePlantSubFieldDataOuterClass {
}
private HomePlantSubFieldData() {
entityIdList_ = emptyIntList();
cAKDDMKAIMD_ = 0;
status_ = 0;
}
@java.lang.Override
@ -118,7 +118,7 @@ public final class HomePlantSubFieldDataOuterClass {
break;
case 24: {
kHFGOPCOAGM_ = input.readUInt32();
gatherPointType_ = input.readUInt32();
break;
}
case 48: {
@ -145,12 +145,12 @@ public final class HomePlantSubFieldDataOuterClass {
case 56: {
int rawValue = input.readEnum();
cAKDDMKAIMD_ = rawValue;
status_ = rawValue;
break;
}
case 64: {
jHFNDBIHLNB_ = input.readUInt32();
seedId_ = input.readUInt32();
break;
}
case 117: {
@ -221,34 +221,34 @@ public final class HomePlantSubFieldDataOuterClass {
}
private int entityIdListMemoizedSerializedSize = -1;
public static final int CAKDDMKAIMD_FIELD_NUMBER = 7;
private int cAKDDMKAIMD_;
public static final int STATUS_FIELD_NUMBER = 7;
private int status_;
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The enum numeric value on the wire for cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @return The enum numeric value on the wire for status.
*/
@java.lang.Override public int getCAKDDMKAIMDValue() {
return cAKDDMKAIMD_;
@java.lang.Override public int getStatusValue() {
return status_;
}
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @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")
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;
}
public static final int JHFNDBIHLNB_FIELD_NUMBER = 8;
private int jHFNDBIHLNB_;
public static final int SEED_ID_FIELD_NUMBER = 8;
private int seedId_;
/**
* <code>uint32 JHFNDBIHLNB = 8;</code>
* @return The jHFNDBIHLNB.
* <code>uint32 seed_id = 8;</code>
* @return The seedId.
*/
@java.lang.Override
public int getJHFNDBIHLNB() {
return jHFNDBIHLNB_;
public int getSeedId() {
return seedId_;
}
public static final int END_TIME_FIELD_NUMBER = 14;
@ -262,15 +262,15 @@ public final class HomePlantSubFieldDataOuterClass {
return endTime_;
}
public static final int KHFGOPCOAGM_FIELD_NUMBER = 3;
private int kHFGOPCOAGM_;
public static final int GATHER_POINT_TYPE_FIELD_NUMBER = 3;
private int gatherPointType_;
/**
* <code>uint32 KHFGOPCOAGM = 3;</code>
* @return The kHFGOPCOAGM.
* <code>uint32 gather_point_type = 3;</code>
* @return The gatherPointType.
*/
@java.lang.Override
public int getKHFGOPCOAGM() {
return kHFGOPCOAGM_;
public int getGatherPointType() {
return gatherPointType_;
}
private byte memoizedIsInitialized = -1;
@ -288,8 +288,8 @@ public final class HomePlantSubFieldDataOuterClass {
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (kHFGOPCOAGM_ != 0) {
output.writeUInt32(3, kHFGOPCOAGM_);
if (gatherPointType_ != 0) {
output.writeUInt32(3, gatherPointType_);
}
if (getEntityIdListList().size() > 0) {
output.writeUInt32NoTag(50);
@ -298,11 +298,11 @@ public final class HomePlantSubFieldDataOuterClass {
for (int i = 0; i < entityIdList_.size(); i++) {
output.writeUInt32NoTag(entityIdList_.getInt(i));
}
if (cAKDDMKAIMD_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
output.writeEnum(7, cAKDDMKAIMD_);
if (status_ != emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus.HOME_FIELD_STATUE_NONE.getNumber()) {
output.writeEnum(7, status_);
}
if (jHFNDBIHLNB_ != 0) {
output.writeUInt32(8, jHFNDBIHLNB_);
if (seedId_ != 0) {
output.writeUInt32(8, seedId_);
}
if (endTime_ != 0) {
output.writeFixed32(14, endTime_);
@ -316,9 +316,9 @@ public final class HomePlantSubFieldDataOuterClass {
if (size != -1) return size;
size = 0;
if (kHFGOPCOAGM_ != 0) {
if (gatherPointType_ != 0) {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(3, kHFGOPCOAGM_);
.computeUInt32Size(3, gatherPointType_);
}
{
int dataSize = 0;
@ -334,13 +334,13 @@ public final class HomePlantSubFieldDataOuterClass {
}
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
.computeEnumSize(7, cAKDDMKAIMD_);
.computeEnumSize(7, status_);
}
if (jHFNDBIHLNB_ != 0) {
if (seedId_ != 0) {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(8, jHFNDBIHLNB_);
.computeUInt32Size(8, seedId_);
}
if (endTime_ != 0) {
size += com.google.protobuf.CodedOutputStream
@ -363,13 +363,13 @@ public final class HomePlantSubFieldDataOuterClass {
if (!getEntityIdListList()
.equals(other.getEntityIdListList())) return false;
if (cAKDDMKAIMD_ != other.cAKDDMKAIMD_) return false;
if (getJHFNDBIHLNB()
!= other.getJHFNDBIHLNB()) return false;
if (status_ != other.status_) return false;
if (getSeedId()
!= other.getSeedId()) return false;
if (getEndTime()
!= other.getEndTime()) return false;
if (getKHFGOPCOAGM()
!= other.getKHFGOPCOAGM()) return false;
if (getGatherPointType()
!= other.getGatherPointType()) return false;
if (!unknownFields.equals(other.unknownFields)) return false;
return true;
}
@ -385,14 +385,14 @@ public final class HomePlantSubFieldDataOuterClass {
hash = (37 * hash) + ENTITY_ID_LIST_FIELD_NUMBER;
hash = (53 * hash) + getEntityIdListList().hashCode();
}
hash = (37 * hash) + CAKDDMKAIMD_FIELD_NUMBER;
hash = (53 * hash) + cAKDDMKAIMD_;
hash = (37 * hash) + JHFNDBIHLNB_FIELD_NUMBER;
hash = (53 * hash) + getJHFNDBIHLNB();
hash = (37 * hash) + STATUS_FIELD_NUMBER;
hash = (53 * hash) + status_;
hash = (37 * hash) + SEED_ID_FIELD_NUMBER;
hash = (53 * hash) + getSeedId();
hash = (37 * hash) + END_TIME_FIELD_NUMBER;
hash = (53 * hash) + getEndTime();
hash = (37 * hash) + KHFGOPCOAGM_FIELD_NUMBER;
hash = (53 * hash) + getKHFGOPCOAGM();
hash = (37 * hash) + GATHER_POINT_TYPE_FIELD_NUMBER;
hash = (53 * hash) + getGatherPointType();
hash = (29 * hash) + unknownFields.hashCode();
memoizedHashCode = hash;
return hash;
@ -532,13 +532,13 @@ public final class HomePlantSubFieldDataOuterClass {
super.clear();
entityIdList_ = emptyIntList();
bitField0_ = (bitField0_ & ~0x00000001);
cAKDDMKAIMD_ = 0;
status_ = 0;
jHFNDBIHLNB_ = 0;
seedId_ = 0;
endTime_ = 0;
kHFGOPCOAGM_ = 0;
gatherPointType_ = 0;
return this;
}
@ -572,10 +572,10 @@ public final class HomePlantSubFieldDataOuterClass {
bitField0_ = (bitField0_ & ~0x00000001);
}
result.entityIdList_ = entityIdList_;
result.cAKDDMKAIMD_ = cAKDDMKAIMD_;
result.jHFNDBIHLNB_ = jHFNDBIHLNB_;
result.status_ = status_;
result.seedId_ = seedId_;
result.endTime_ = endTime_;
result.kHFGOPCOAGM_ = kHFGOPCOAGM_;
result.gatherPointType_ = gatherPointType_;
onBuilt();
return result;
}
@ -634,17 +634,17 @@ public final class HomePlantSubFieldDataOuterClass {
}
onChanged();
}
if (other.cAKDDMKAIMD_ != 0) {
setCAKDDMKAIMDValue(other.getCAKDDMKAIMDValue());
if (other.status_ != 0) {
setStatusValue(other.getStatusValue());
}
if (other.getJHFNDBIHLNB() != 0) {
setJHFNDBIHLNB(other.getJHFNDBIHLNB());
if (other.getSeedId() != 0) {
setSeedId(other.getSeedId());
}
if (other.getEndTime() != 0) {
setEndTime(other.getEndTime());
}
if (other.getKHFGOPCOAGM() != 0) {
setKHFGOPCOAGM(other.getKHFGOPCOAGM());
if (other.getGatherPointType() != 0) {
setGatherPointType(other.getGatherPointType());
}
this.mergeUnknownFields(other.unknownFields);
onChanged();
@ -755,87 +755,87 @@ public final class HomePlantSubFieldDataOuterClass {
return this;
}
private int cAKDDMKAIMD_ = 0;
private int status_ = 0;
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The enum numeric value on the wire for cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @return The enum numeric value on the wire for status.
*/
@java.lang.Override public int getCAKDDMKAIMDValue() {
return cAKDDMKAIMD_;
@java.lang.Override public int getStatusValue() {
return status_;
}
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @param value The enum numeric value on the wire for cAKDDMKAIMD to set.
* <code>.HomePlantFieldStatus status = 7;</code>
* @param value The enum numeric value on the wire for status to set.
* @return This builder for chaining.
*/
public Builder setCAKDDMKAIMDValue(int value) {
public Builder setStatusValue(int value) {
cAKDDMKAIMD_ = value;
status_ = value;
onChanged();
return this;
}
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @return The cAKDDMKAIMD.
* <code>.HomePlantFieldStatus status = 7;</code>
* @return The status.
*/
@java.lang.Override
public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getCAKDDMKAIMD() {
public emu.grasscutter.net.proto.HomePlantFieldStatusOuterClass.HomePlantFieldStatus getStatus() {
@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;
}
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* @param value The cAKDDMKAIMD to set.
* <code>.HomePlantFieldStatus status = 7;</code>
* @param value The status to set.
* @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) {
throw new NullPointerException();
}
cAKDDMKAIMD_ = value.getNumber();
status_ = value.getNumber();
onChanged();
return this;
}
/**
* <code>.HomePlantFieldStatus CAKDDMKAIMD = 7;</code>
* <code>.HomePlantFieldStatus status = 7;</code>
* @return This builder for chaining.
*/
public Builder clearCAKDDMKAIMD() {
public Builder clearStatus() {
cAKDDMKAIMD_ = 0;
status_ = 0;
onChanged();
return this;
}
private int jHFNDBIHLNB_ ;
private int seedId_ ;
/**
* <code>uint32 JHFNDBIHLNB = 8;</code>
* @return The jHFNDBIHLNB.
* <code>uint32 seed_id = 8;</code>
* @return The seedId.
*/
@java.lang.Override
public int getJHFNDBIHLNB() {
return jHFNDBIHLNB_;
public int getSeedId() {
return seedId_;
}
/**
* <code>uint32 JHFNDBIHLNB = 8;</code>
* @param value The jHFNDBIHLNB to set.
* <code>uint32 seed_id = 8;</code>
* @param value The seedId to set.
* @return This builder for chaining.
*/
public Builder setJHFNDBIHLNB(int value) {
public Builder setSeedId(int value) {
jHFNDBIHLNB_ = value;
seedId_ = value;
onChanged();
return this;
}
/**
* <code>uint32 JHFNDBIHLNB = 8;</code>
* <code>uint32 seed_id = 8;</code>
* @return This builder for chaining.
*/
public Builder clearJHFNDBIHLNB() {
public Builder clearSeedId() {
jHFNDBIHLNB_ = 0;
seedId_ = 0;
onChanged();
return this;
}
@ -871,33 +871,33 @@ public final class HomePlantSubFieldDataOuterClass {
return this;
}
private int kHFGOPCOAGM_ ;
private int gatherPointType_ ;
/**
* <code>uint32 KHFGOPCOAGM = 3;</code>
* @return The kHFGOPCOAGM.
* <code>uint32 gather_point_type = 3;</code>
* @return The gatherPointType.
*/
@java.lang.Override
public int getKHFGOPCOAGM() {
return kHFGOPCOAGM_;
public int getGatherPointType() {
return gatherPointType_;
}
/**
* <code>uint32 KHFGOPCOAGM = 3;</code>
* @param value The kHFGOPCOAGM to set.
* <code>uint32 gather_point_type = 3;</code>
* @param value The gatherPointType to set.
* @return This builder for chaining.
*/
public Builder setKHFGOPCOAGM(int value) {
public Builder setGatherPointType(int value) {
kHFGOPCOAGM_ = value;
gatherPointType_ = value;
onChanged();
return this;
}
/**
* <code>uint32 KHFGOPCOAGM = 3;</code>
* <code>uint32 gather_point_type = 3;</code>
* @return This builder for chaining.
*/
public Builder clearKHFGOPCOAGM() {
public Builder clearGatherPointType() {
kHFGOPCOAGM_ = 0;
gatherPointType_ = 0;
onChanged();
return this;
}
@ -969,12 +969,12 @@ public final class HomePlantSubFieldDataOuterClass {
static {
java.lang.String[] descriptorData = {
"\n\033HomePlantSubFieldData.proto\032\032HomePlant" +
"FieldStatus.proto\"\227\001\n\025HomePlantSubFieldD" +
"ata\022\026\n\016entity_id_list\030\006 \003(\r\022*\n\013CAKDDMKAI" +
"MD\030\007 \001(\0162\025.HomePlantFieldStatus\022\023\n\013JHFND" +
"BIHLNB\030\010 \001(\r\022\020\n\010end_time\030\016 \001(\007\022\023\n\013KHFGOP" +
"COAGM\030\003 \001(\rB\033\n\031emu.grasscutter.net.proto" +
"b\006proto3"
"FieldStatus.proto\"\224\001\n\025HomePlantSubFieldD" +
"ata\022\026\n\016entity_id_list\030\006 \003(\r\022%\n\006status\030\007 " +
"\001(\0162\025.HomePlantFieldStatus\022\017\n\007seed_id\030\010 " +
"\001(\r\022\020\n\010end_time\030\016 \001(\007\022\031\n\021gather_point_ty" +
"pe\030\003 \001(\rB\033\n\031emu.grasscutter.net.protob\006p" +
"roto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
@ -986,7 +986,7 @@ public final class HomePlantSubFieldDataOuterClass {
internal_static_HomePlantSubFieldData_fieldAccessorTable = new
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
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();
}

View File

@ -1,8 +1,32 @@
# Handbook Data
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
- `mainquests.csv'
- `mainquests.csv`
- `commands.json`
- `entities.csv`
- `avatars.csv`

View File

@ -1,8 +1,5 @@
package emu.grasscutter;
import static emu.grasscutter.config.Configuration.SERVER;
import static emu.grasscutter.utils.lang.Language.translate;
import ch.qos.logback.classic.*;
import emu.grasscutter.auth.*;
import emu.grasscutter.command.*;
@ -21,16 +18,20 @@ import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.*;
import emu.grasscutter.utils.lang.Language;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.io.*;
import java.util.Calendar;
import java.util.concurrent.*;
import javax.annotation.Nullable;
import lombok.*;
import org.jline.reader.*;
import org.jline.terminal.*;
import org.reflections.Reflections;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.*;
import java.util.Calendar;
import java.util.concurrent.*;
import static emu.grasscutter.config.Configuration.SERVER;
import static emu.grasscutter.utils.lang.Language.translate;
public final class Grasscutter {
public static final File configFile = new File("./config.json");
public static final Reflections reflector = new Reflections("emu.grasscutter");
@ -181,12 +182,18 @@ public final class Grasscutter {
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
// Start database heartbeat.
Database.startSaveThread();
// Open console.
Grasscutter.startConsole();
}
/** Server shutdown event. */
private static void onShutdown() {
// Save all data.
Database.saveAll();
// Disable all plugins.
if (pluginManager != null) pluginManager.disablePlugins();
// Shutdown the game server.
@ -196,14 +203,14 @@ public final class Grasscutter {
// Wait for Grasscutter's thread pool to finish.
var executor = Grasscutter.getThreadPool();
executor.shutdown();
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
// Wait for database operations to finish.
var dbExecutor = DatabaseHelper.getEventExecutor();
dbExecutor.shutdown();
if (!dbExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
if (!dbExecutor.awaitTermination(2, TimeUnit.MINUTES)) {
dbExecutor.shutdownNow();
}
} catch (InterruptedException ignored) {

View File

@ -33,7 +33,7 @@ public final class EnterDungeonCommand implements CommandHandler {
targetPlayer
.getServer()
.getDungeonSystem()
.enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId);
.enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId, true);
if (!result) {
CommandHandler.sendMessage(

View File

@ -0,0 +1,105 @@
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> <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.size() == 0) {
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 {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return;
}
}
val userVal = value;
var sceneData =
sceneTagData.values().stream().filter(sceneTag -> sceneTag.getId() == userVal).findFirst();
if (sceneData == null) {
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 -> {
if (targetPlayer.getSceneTags().get(sceneTag.getSceneId()) == null) {
targetPlayer.getSceneTags().put(sceneTag.getSceneId(), new HashSet<>());
}
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
});
// Remove default SceneTags, as most are "before" or "locked" states
allData.stream()
.filter(sceneTag -> sceneTag.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 setSceneTags(Player targetPlayer) {
targetPlayer.sendPacket(new PacketPlayerWorldSceneInfoListNotify(targetPlayer));
}
}

View File

@ -33,9 +33,11 @@ public class ConfigContainer {
* Lua script require system if performance is a concern.
* Version 12 - 'http.startImmediately' was added to control whether the
* 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() {
return 12;
return 13;
}
/**
@ -169,6 +171,9 @@ public class ConfigContainer {
/* This is the port used in the default region. */
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 */
public int loadEntitiesForPlayerRange = 300;
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */

View File

@ -286,6 +286,10 @@ public final class GameData {
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<HomeWorldEventData> homeWorldEventDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
new Int2ObjectOpenHashMap<>();

View File

@ -273,18 +273,20 @@ public class AbilityModifier implements Serializable {
@SerializedName(
value = "amount",
alternate = {"PDLLIFICICJ", "cdRatio"})
alternate = {"LNFMOCKIAGK", "PDLLIFICICJ", "cdRatio"})
public DynamicFloat amount = DynamicFloat.ZERO;
@SerializedName(value = "amountByTargetCurrentHPRatio")
@SerializedName(
value = "amountByTargetCurrentHPRatio",
alternate = {"GMFELAKANEF"})
public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO;
@SerializedName(value = "unused")
@SerializedName(value = "unknown2")
public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO;
@SerializedName(
value = "unknown",
alternate = {"HFNJHOGGFKB", "GEJGGCIOLKN"})
value = "amountByCasterMaxHPRatio",
alternate = {"PKPBLCNMPIG", "HFNJHOGGFKB", "GEJGGCIOLKN"})
public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByGetDamage = DynamicFloat.ZERO;
@ -292,7 +294,7 @@ public class AbilityModifier implements Serializable {
@SerializedName(value = "amountByTargetMaxHPRatio")
public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
@SerializedName(value = "amountByCasterMaxHPRatio")
@SerializedName(value = "unknown1", alternate = "GGLMMJHNGMO")
public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO;
public DynamicFloat limboByTargetMaxHPRatio = DynamicFloat.ZERO;

View File

@ -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;
}

View File

@ -22,6 +22,7 @@ public class DungeonData extends GameResource {
private DungeonInvolveType involveType;
@Getter private int limitLevel;
@Getter private int passCond;
@Getter private int passJumpDungeon;
@Getter private int reviveMaxCount;
@Getter private int settleCountdownTime;
@Getter private int failSettleCountdownTime;

View File

@ -0,0 +1,85 @@
package emu.grasscutter.database;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.objects.DatabaseObject;
import org.slf4j.*;
import java.util.*;
import java.util.concurrent.*;
/**
* Complicated manager of the MongoDB database.
* Handles caching, data operations, and more.
*/
public interface Database {
Logger logger = LoggerFactory.getLogger("Database");
List<DatabaseObject<?>> objects = new CopyOnWriteArrayList<>();
/**
* Queues an object to be saved.
*
* @param object The object to save.
*/
static void save(DatabaseObject<?> object) {
if (object.saveImmediately()) {
object.save();
} else {
objects.add(object);
}
}
/**
* Performs a bulk save of all deferred objects.
*/
static void saveAll() {
var size = objects.size();
Database.saveAll(objects);
logger.debug("Performed auto save on {} objects.", size);
}
/**
* Performs a bulk save of all deferred objects.
*
* @param objects The objects to save.
*/
static void saveAll(List<? extends DatabaseObject<?>> objects) {
// Sort all objects into their respective databases.
var gameObjects = objects.stream()
.filter(DatabaseObject::isGameObject)
.toList();
var accountObjects = objects.stream()
.filter(o -> !o.isGameObject())
.toList();
// Clear the collective list.
objects.clear();
// Save all objects.
var executor = DatabaseHelper.getEventExecutor();
if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.DISPATCH_ONLY) {
executor.submit(() -> {
DatabaseManager.getGameDatastore().save(gameObjects);
});
}
if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.GAME_ONLY) {
executor.submit(() -> {
DatabaseManager.getAccountDatastore().save(accountObjects);
});
}
}
/**
* Starts the auto-save thread.
* Runs every 5 minutes.
*/
static void startSaveThread() {
var timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Database.saveAll();
}
}, 0, 1000 * 60 * 5);
}
}

View File

@ -1,7 +1,5 @@
package emu.grasscutter.database;
import static com.mongodb.client.model.Filters.eq;
import dev.morphia.query.*;
import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.*;
@ -20,24 +18,19 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.utils.objects.Returnable;
import io.netty.util.concurrent.FastThreadLocalThread;
import lombok.Getter;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import lombok.Getter;
import static com.mongodb.client.model.Filters.eq;
public final class DatabaseHelper {
@Getter
private static final ExecutorService eventExecutor =
new ThreadPoolExecutor(
6,
6,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
FastThreadLocalThread::new,
new ThreadPoolExecutor.AbortPolicy());
Executors.newFixedThreadPool(4);
/**
* Saves an object on the account datastore.

View File

@ -6,6 +6,7 @@ import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.game.ability.Ability;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
@AbilityAction(AbilityModifierAction.Type.HealHP)
public final class ActionHealHP extends AbilityActionHandler {
@ -31,24 +32,25 @@ public final class ActionHealHP extends AbilityActionHandler {
if (owner == null) return false;
ability
.getAbilitySpecials()
.forEach((k, v) -> Grasscutter.getLogger().trace(">>> {}: {}", k, v));
// Get all properties.
var properties = new Object2FloatOpenHashMap<String>();
// 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);
var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(ability);
var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(ability);
var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(ability);
var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(ability);
// Calculate ratios from properties.
var amountByCasterMaxHPRatio = action.amountByCasterMaxHPRatio.get(properties, 0);
var amountByCasterAttackRatio = action.amountByCasterAttackRatio.get(properties, 0);
var amountByCasterCurrentHPRatio = action.amountByCasterCurrentHPRatio.get(properties, 0);
var amountByTargetCurrentHPRatio = action.amountByTargetCurrentHPRatio.get(properties, 0);
var amountByTargetMaxHPRatio = action.amountByTargetMaxHPRatio.get(properties, 0);
Grasscutter.getLogger().trace("amountByCasterMaxHPRatio: " + amountByCasterMaxHPRatio);
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);
var amountToRegenerate = action.amount.get(properties, 0);
amountToRegenerate +=
amountByCasterMaxHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
@ -57,25 +59,17 @@ public final class ActionHealHP extends AbilityActionHandler {
amountToRegenerate +=
amountByCasterCurrentHPRatio * owner.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
Grasscutter.getLogger().trace("amountToRegenerate: " + amountToRegenerate);
var abilityRatio = 1.0f;
Grasscutter.getLogger().trace("Base abilityRatio: " + abilityRatio);
if (!action.ignoreAbilityProperty)
abilityRatio +=
target.getFightProperty(FightProperty.FIGHT_PROP_HEAL_ADD)
+ target.getFightProperty(FightProperty.FIGHT_PROP_HEALED_ADD);
Grasscutter.getLogger().trace("abilityRatio: " + abilityRatio);
Grasscutter.getLogger().trace("Sub-regenerate amount: " + amountToRegenerate);
amountToRegenerate +=
amountByTargetCurrentHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
amountToRegenerate +=
amountByTargetMaxHPRatio * target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
Grasscutter.getLogger().trace("Healing {} without ratios", amountToRegenerate);
target.heal(
amountToRegenerate * abilityRatio * action.healRatio.get(ability, 1f),
action.muteHealEffect);

View File

@ -12,12 +12,13 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.AchievementOuterClass.Achievement.Status;
import emu.grasscutter.server.event.player.PlayerCompleteAchievementEvent;
import emu.grasscutter.server.packet.send.*;
import lombok.*;
import org.bson.types.ObjectId;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import javax.annotation.Nullable;
import lombok.*;
import org.bson.types.ObjectId;
@Entity("achievements")
@Data
@ -44,15 +45,30 @@ public class Achievements {
return achievements;
}
public static Achievements create(int uid) {
var newAchievement =
Achievements.of()
.uid(uid)
/**
* Creates a blank achievements object.
*
* @return The achievements object.
*/
public static Achievements blank() {
return Achievements.of()
.achievementList(init())
.finishedAchievementNum(0)
.takenGoalRewardIdList(Lists.newArrayList())
.build();
}
/**
* Creates and saves a blank achievements object.
*
* @param uid The UID of the player.
* @return The achievements object.
*/
public static Achievements create(int uid) {
var newAchievement = blank();
newAchievement.setUid(uid);
newAchievement.save();
return newAchievement;
}

View File

@ -8,10 +8,9 @@ import emu.grasscutter.game.player.*;
import emu.grasscutter.game.props.*;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
import lombok.Getter;
import java.util.*;
import java.util.concurrent.*;
import lombok.Getter;
@SuppressWarnings("unchecked")
@Getter

View File

@ -11,15 +11,17 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import java.util.*;
import emu.grasscutter.utils.objects.DatabaseObject;
import lombok.*;
import lombok.experimental.FieldDefaults;
import java.util.*;
@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
public class PlayerActivityData implements DatabaseObject<PlayerActivityData> {
@Id String id;
int uid;
int activityId;
@ -34,8 +36,25 @@ public class PlayerActivityData {
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
}
/**
* Saves this object to the database.
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
*/
public void save() {
DatabaseHelper.savePlayerActivityData(this);
this.deferSave();
}
/**
* Saves this object to the database.
*
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
*/
public void save(boolean immediate) {
if (immediate) {
DatabaseObject.super.save();
} else {
this.save();
}
}
public synchronized void addWatcherProgress(int watcherId) {

View File

@ -80,7 +80,7 @@ public class TrialAvatarActivityHandler extends ActivityHandler {
if (!player
.getServer()
.getDungeonSystem()
.enterDungeon(player, enterPointId, getTrialActivityDungeonId(trialAvatarIndexId)))
.enterDungeon(player, enterPointId, getTrialActivityDungeonId(trialAvatarIndexId), true))
return false;
setSelectedTrialAvatarIndex(trialAvatarIndexId);

View File

@ -13,7 +13,6 @@ import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData.InherentProudSkil
import emu.grasscutter.data.excels.reliquary.*;
import emu.grasscutter.data.excels.trial.TrialAvatarTemplateData;
import emu.grasscutter.data.excels.weapon.*;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.player.Player;
@ -29,6 +28,7 @@ import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGra
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.helpers.ProtoHelper;
import emu.grasscutter.utils.objects.DatabaseObject;
import it.unimi.dsi.fastutil.ints.*;
import lombok.*;
import org.bson.types.ObjectId;
@ -40,7 +40,7 @@ import java.util.stream.Stream;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Entity(value = "avatars", useDiscriminator = false)
public class Avatar {
public class Avatar implements DatabaseObject<Avatar> {
@Transient @Getter private final Int2ObjectMap<GameItem> equips;
@Transient @Getter private final Int2FloatOpenHashMap fightProperties;
@Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides;
@ -990,8 +990,25 @@ public class Avatar {
return entity != null ? entity.getId() : 0;
}
/**
* Saves this object to the database.
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
*/
public void save() {
DatabaseHelper.saveAvatar(this);
this.deferSave();
}
/**
* Saves this object to the database.
*
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
*/
public void save(boolean immediate) {
if (immediate) {
DatabaseObject.super.save();
} else {
this.save();
}
}
public AvatarInfo toProto() {

View File

@ -60,7 +60,7 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
avatar.save(true);
return true;
}
@ -165,7 +165,7 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
if ((avatar.getAvatarId() == 10000007) || (avatar.getAvatarId() == 10000005)) {
avatar.setSkillDepot(skillDepot);
avatar.setSkillDepotData(skillDepot);
avatar.save();
avatar.save(true);
}
}

View File

@ -14,11 +14,12 @@ import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePass
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.*;
import lombok.Getter;
import org.bson.types.ObjectId;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@ -40,7 +41,10 @@ public class BattlePassManager extends BasePlayerDataManager {
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
this.missions = new HashMap<>();
this.takenRewards = new HashMap<>();
}
public void setPlayer(Player player) {

View File

@ -282,6 +282,16 @@ public final class DungeonManager {
// Call PlayerFinishDungeonEvent.
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() {

View File

@ -88,7 +88,7 @@ public final class DungeonSystem extends BaseGameSystem {
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);
if (data == null) {
@ -103,7 +103,7 @@ public final class DungeonSystem extends BaseGameSystem {
var sceneId = data.getSceneId();
var scene = player.getScene();
scene.setPrevScene(sceneId);
if (savePrevious) scene.setPrevScene(scene.getId());
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
scene = player.getScene();
@ -111,7 +111,7 @@ public final class DungeonSystem extends BaseGameSystem {
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
}
scene.setPrevScenePoint(pointId);
if (savePrevious) scene.setPrevScenePoint(pointId);
return true;
}

View File

@ -88,11 +88,6 @@ public class EntityAvatar extends GameEntity {
return getPlayer().getRotation();
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public Int2FloatMap getFightProperties() {
return getAvatar().getFightProperties();
@ -137,13 +132,19 @@ public class EntityAvatar extends GameEntity {
@Override
public float heal(float amount, boolean mute) {
// Do not heal character if they are dead
if (!this.isAlive()) {
// Do not heal character if they are dead.
var currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
if (currentHp <= 0) {
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) {
getScene()
.broadcastPacket(

View File

@ -256,6 +256,9 @@ public class EntityGadget extends EntityBaseGadget {
var route = this.getScene().getSceneRouteById(configRoute.getRouteId());
if (route != null) {
var points = route.getPoints();
if (configRoute.getStartIndex() == points.length - 1) {
configRoute.setStartIndex(0);
}
val currIndex = configRoute.getStartIndex();
Position prevpos;
@ -301,6 +304,9 @@ public class EntityGadget extends EntityBaseGadget {
}
configRoute.setStartIndex(I);
this.position.set(points[I].getPos());
if (I == points.length - 1) {
configRoute.setStarted(false);
}
},
(int) time));
}

View File

@ -8,6 +8,7 @@ import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.Getter;
public class EntityHomeAnimal extends EntityMonster implements Rebornable {
@ -15,7 +16,7 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
private final Position rebornPos;
@Getter private final int rebirth;
@Getter private final int rebirthCD;
private boolean disappeared;
private final AtomicBoolean disappeared = new AtomicBoolean();
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos) {
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, 1);
@ -60,13 +61,13 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
new PacketSceneEntityDisappearNotify(
this, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
this.rebornCDTickCount = this.getRebornCD();
this.disappeared = true;
this.disappeared.set(true);
}
@Override
public void reborn() {
if (this.disappeared) {
this.disappeared = false;
if (this.disappeared.get()) {
this.disappeared.set(false);
this.getPosition().set(this.getRebornPos());
this.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(this));
}
@ -74,6 +75,6 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
@Override
public boolean isInCD() {
return this.disappeared;
return this.disappeared.get();
}
}

View File

@ -15,9 +15,10 @@ import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.server.event.entity.*;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import it.unimi.dsi.fastutil.ints.*;
import java.util.*;
import lombok.*;
import java.util.*;
public abstract class GameEntity {
@Getter private final Scene scene;
@Getter protected int id;
@ -33,6 +34,9 @@ public abstract class GameEntity {
@Getter @Setter private boolean lockHP;
@Setter(AccessLevel.PROTECTED)
@Getter private boolean isDead = false;
// Lua controller for specific actions
@Getter @Setter private EntityController entityController;
@Getter private ElementType lastAttackType = ElementType.None;
@ -63,7 +67,7 @@ public abstract class GameEntity {
}
public boolean isAlive() {
return true;
return !this.isDead;
}
public LifeState getLifeState() {
@ -172,10 +176,9 @@ public abstract class GameEntity {
this.lastAttackType = attackType;
// 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.isDead = true;
}
this.runLuaCallbacks(event);
@ -186,7 +189,7 @@ public abstract class GameEntity {
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead.
if (isDead) {
if (this.isDead) {
this.getScene().killEntity(this, killerId);
}
}

View File

@ -17,6 +17,7 @@ import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
@ -55,6 +56,7 @@ public class GameHome {
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
Map<Integer, Set<Integer>> finishedTalkIdMap;
Set<Integer> finishedRewardEventIdSet;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
@ -62,7 +64,9 @@ public class GameHome {
home = GameHome.create(uid);
}
home.reassignIfNull();
home.fixMainHouseIfOld();
home.syncHomeAvatarCostume();
return home;
}
@ -79,9 +83,19 @@ public class GameHome {
.mainHouseMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.finishedTalkIdMap(new HashMap<>())
.finishedRewardEventIdSet(new HashSet<>())
.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.
private void fixMainHouseIfOld() {
if (this.getMainHouseMap() == null) {
@ -97,6 +111,18 @@ public class GameHome {
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() {
DatabaseHelper.saveHome(this);
}
@ -113,12 +139,12 @@ public class GameHome {
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
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.getMainHouseItem(outdoor); // put new main house with default arrangement.
this.save();
this.getPlayer().getCurHomeWorld().getModuleManager().refreshMainHouse();
}
public void onOwnerLogin(Player player) {
@ -160,6 +188,8 @@ public class GameHome {
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAvatarTalkFinishInfoNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
player.getSession().send(new PacketHomeAvatarRewardEventNotify(player));
player.getSession().send(new PacketHomeAvatarAllFinishRewardNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
@ -226,6 +256,20 @@ public class GameHome {
.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) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
@ -404,7 +448,7 @@ public class GameHome {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
storedCoin = Math.min(maxCoin, newCoin);
}
// Update fetter exp
@ -416,7 +460,7 @@ public class GameHome {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
storedFetterExp = Math.min(maxFetter, newFetter);
}
save();

View File

@ -2,6 +2,8 @@ package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
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 java.util.*;
import java.util.stream.Stream;
@ -19,6 +21,7 @@ public class HomeBlockItem {
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
List<HomeSuiteItem> suiteList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
@ -37,10 +40,11 @@ public class HomeBlockItem {
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.suiteList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo, Player owner) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList =
@ -60,7 +64,12 @@ public class HomeBlockItem {
this.deployNPCList =
homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.map(homeNpcData -> HomeNPCItem.parseFrom(homeNpcData, owner))
.toList();
this.suiteList =
homeBlockArrangementInfo.getFurnitureSuiteListList().stream()
.map(HomeSuiteItem::parseFrom)
.toList();
}
@ -81,15 +90,20 @@ public class HomeBlockItem {
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
this.suiteList.forEach(f -> proto.addFurnitureSuiteList(f.toProto()));
return proto.build();
}
// TODO add more types (farm field and suite)
// TODO implement farm field.
public List<? extends HomeMarkPointProtoFactory> getMarkPointProtoFactories() {
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)
.toList();
}
@ -107,5 +121,8 @@ public class HomeBlockItem {
if (this.deployNPCList == null) {
this.deployNPCList = List.of();
}
if (this.suiteList == null) {
this.suiteList = List.of();
}
}
}

View 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();
}
}

View File

@ -2,6 +2,7 @@ package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointNPCDataOuterClass;
@ -23,11 +24,12 @@ public class HomeNPCItem implements HomeMarkPointProtoFactory {
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData, Player owner) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(owner.getCostumeFrom(homeNpcData.getAvatarId()))
.build();
}

View File

@ -1,9 +1,12 @@
package emu.grasscutter.game.home;
import emu.grasscutter.data.excels.scene.SceneData;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.Rebornable;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
public class HomeScene extends Scene {
@ -40,10 +43,31 @@ public class HomeScene extends Scene {
.forEach(gameEntity -> gameEntity.onTick(this.getSceneTimeSeconds()));
this.finishLoading();
this.checkPlayerRespawn();
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
public void checkNpcGroup() {}

View File

@ -6,16 +6,18 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@ -49,14 +51,14 @@ public class HomeSceneItem {
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
public void update(HomeSceneArrangementInfo arrangementInfo, Player owner) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
block.update(blockItem, owner);
this.blockItems.put(blockItem.getBlockId(), block);
}
@ -84,17 +86,13 @@ public class HomeSceneItem {
}
@Nullable public Position getTeleportPointPos(int guid) {
var pos = new AtomicReference<Position>();
this.getBlockItems().values().stream()
return this.getBlockItems().values().stream()
.map(HomeBlockItem::getDeployFurnitureList)
.flatMap(Collection::stream)
.filter(homeFurnitureItem -> homeFurnitureItem.getGuid() == guid)
.map(HomeFurnitureItem::getSpawnPos)
.findFirst()
.ifPresent(pos::set);
return pos.get();
.orElse(null);
}
public List<EntityHomeAnimal> getAnimals(Scene scene) {

View File

@ -8,33 +8,66 @@ import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ChatInfoOuterClass;
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.function.Consumer;
import lombok.Getter;
@Getter
public class HomeWorld extends World {
@Getter private final GameHome home;
private final GameHome home;
private HomeModuleManager moduleManager;
public HomeWorld(GameServer server, Player owner) {
super(server, owner);
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
this.refreshModuleManager();
server.registerHomeWorld(this);
}
@Override
public void registerScene(Scene scene) {
this.addAnimalsToScene((HomeScene) scene);
super.registerScene(scene);
public boolean onTick() {
if (this.moduleManager == null) {
return false;
}
this.moduleManager.tick();
if (this.getTickCount() % 10 == 0) {
this.getPlayers().forEach(p -> p.sendPacket(new PacketPlayerGameTimeNotify(p)));
}
@Override
public void deregisterScene(Scene scene) {
super.deregisterScene(scene);
if (this.isInHome(this.getHost()) && this.getTickCount() % 60 == 0) {
this.getHost().updatePlayerGameTime(this.getCurrentWorldTime());
}
private void addAnimalsToScene(HomeScene scene) {
scene.getSceneItem().getAnimals(scene).forEach(scene::addEntity);
this.tickCount++;
return false;
}
public void refreshModuleManager() {
if (this.moduleManager != null) {
this.moduleManager.onRemovedModule();
}
this.moduleManager = new HomeModuleManager(this);
this.moduleManager.onSetModule();
}
public int getActiveOutdoorSceneId() {
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
@ -188,6 +221,12 @@ public class HomeWorld extends World {
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) {
if (this.getHost().isOnline()) {
this.getHost().sendPacket(basePacket);

View File

@ -5,10 +5,7 @@ import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.World;
import emu.grasscutter.game.world.data.TeleportProperties;
import emu.grasscutter.net.proto.EnterTypeOuterClass;
import emu.grasscutter.net.proto.OtherPlayerEnterHomeNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerApplyEnterHomeResultNotifyOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.server.event.player.PlayerEnterHomeEvent;
import emu.grasscutter.server.event.player.PlayerLeaveHomeEvent;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
@ -215,6 +212,10 @@ public class HomeWorldMPSystem extends BaseGameSystem {
player.setCurHomeWorld(myHome);
myHome.getHome().onOwnerLogin(player);
player.sendPacket(
new PacketPlayerQuitFromHomeNotify(
PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason
.BACK_TO_MY_WORLD));
player.sendPacket(
new PacketPlayerEnterSceneNotify(
player,
@ -263,6 +264,9 @@ public class HomeWorldMPSystem extends BaseGameSystem {
victim.setCurHomeWorld(myHome);
myHome.getHome().onOwnerLogin(victim);
victim.sendPacket(
new PacketPlayerQuitFromHomeNotify(
PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason.KICK_BY_HOST));
victim.sendPacket(
new PacketPlayerEnterSceneNotify(
victim,

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -20,13 +20,14 @@ import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary;
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.objects.WeightedList;
import java.util.*;
import emu.grasscutter.utils.objects.*;
import lombok.*;
import org.bson.types.ObjectId;
import java.util.*;
@Entity(value = "items", useDiscriminator = false)
public class GameItem {
public class GameItem implements DatabaseObject<GameItem> {
@Id private ObjectId id;
@Indexed private int ownerId;
@Getter @Setter private int itemId;
@ -261,14 +262,36 @@ public class GameItem {
}
}
/**
* Saves this object to the database.
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
*/
public void save() {
if (this.count > 0 && this.ownerId > 0) {
DatabaseHelper.saveItem(this);
} else if (this.getObjectId() != null) {
this.deferSave();
} else {
DatabaseHelper.deleteItem(this);
}
}
/**
* Saves this object to the database.
*
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
*/
public void save(boolean immediate) {
if (this.count < 0 || this.ownerId <= 0) {
DatabaseHelper.deleteItem(this);
return;
}
if (immediate) {
DatabaseObject.super.save();
} else {
this.save();
}
}
public SceneWeaponInfo createSceneWeaponInfo() {
var weaponInfo =
SceneWeaponInfo.newBuilder()

View File

@ -1,7 +1,5 @@
package emu.grasscutter.game.inventory;
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
@ -18,10 +16,13 @@ import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.longs.*;
import java.util.*;
import javax.annotation.Nullable;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*;
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
private final Long2ObjectMap<GameItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
@ -178,7 +179,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
changedItems.add(result);
}
}
if (changedItems.size() == 0) {
if (changedItems.isEmpty()) {
return;
}
if (reason != null) {
@ -216,14 +217,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.
* @return True if the player has the items, false otherwise.
*/
public boolean hasAllItems(Collection<ItemParam> 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;
@ -298,8 +299,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
// Add
switch (type) {
case ITEM_WEAPON:
case ITEM_RELIQUARY:
case ITEM_WEAPON, ITEM_RELIQUARY -> {
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
@ -310,23 +310,23 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
// Set ownership and save to db
item.save();
return item;
case ITEM_VIRTUAL:
}
case ITEM_VIRTUAL -> {
// Handle
this.addVirtualItem(item.getItemId(), item.getCount());
return item;
default:
}
default -> {
switch (item.getItemData().getMaterialType()) {
case MATERIAL_AVATAR:
case MATERIAL_FLYCLOAK:
case MATERIAL_COSTUME:
case MATERIAL_NAMECARD:
case MATERIAL_AVATAR, MATERIAL_FLYCLOAK, MATERIAL_COSTUME, MATERIAL_NAMECARD -> {
Grasscutter.getLogger()
.warn(
"Attempted to add a "
+ item.getItemData().getMaterialType().name()
+ " to inventory, but item definition lacks isUseOnGain. This indicates a Resources error.");
return null;
default:
}
default -> {
if (tab == null) {
return null;
}
@ -353,6 +353,8 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
}
}
}
}
}
private synchronized void putItem(GameItem item, InventoryTab tab) {
this.player.getCodex().checkAddedItem(item);

View File

@ -8,7 +8,7 @@ import emu.grasscutter.data.excels.world.WeatherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.*;
import emu.grasscutter.game.ability.AbilityManager;
import emu.grasscutter.game.achievement.Achievements;
import emu.grasscutter.game.achievement.*;
import emu.grasscutter.game.activity.ActivityManager;
import emu.grasscutter.game.avatar.*;
import emu.grasscutter.game.battlepass.BattlePassManager;
@ -55,7 +55,7 @@ import emu.grasscutter.server.game.GameSession.SessionState;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.*;
import emu.grasscutter.utils.helpers.DateHelper;
import emu.grasscutter.utils.objects.FieldFetch;
import emu.grasscutter.utils.objects.*;
import it.unimi.dsi.fastutil.ints.*;
import lombok.*;
@ -66,7 +66,7 @@ import java.util.concurrent.*;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Entity(value = "players", useDiscriminator = false)
public class Player implements PlayerHook, FieldFetch {
public class Player implements DatabaseObject<Player>, PlayerHook, FieldFetch {
@Id private int id;
@Indexed(options = @IndexOptions(unique = true))
@Getter private String accountId;
@ -116,6 +116,7 @@ public class Player implements PlayerHook, FieldFetch {
@Getter private Map<Integer, ActiveCookCompoundData> activeCookCompounds;
@Getter private Map<Integer, Integer> questGlobalVariables;
@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>> unlockedScenePoints;
@Getter @Setter private List<Integer> chatEmojiIdList;
@ -244,6 +245,7 @@ public class Player implements PlayerHook, FieldFetch {
this.unlockedRecipies = new HashMap<>();
this.questGlobalVariables = new HashMap<>();
this.openStates = new HashMap<>();
this.sceneTags = new HashMap<>();
this.unlockedSceneAreas = new HashMap<>();
this.unlockedScenePoints = new HashMap<>();
this.chatEmojiIdList = new ArrayList<>();
@ -259,6 +261,7 @@ public class Player implements PlayerHook, FieldFetch {
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
this.birthday = new PlayerBirthday();
this.achievements = Achievements.blank();
this.rewardedLevels = new HashSet<>();
this.homeRewardedLevels = new HashSet<>();
this.seenRealmList = new HashSet<>();
@ -273,8 +276,10 @@ public class Player implements PlayerHook, FieldFetch {
this.energyManager = new EnergyManager(this);
this.resinManager = new ResinManager(this);
this.forgingManager = new ForgingManager(this);
this.deforestationManager = new DeforestationManager(this);
this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this);
this.battlePassManager = new BattlePassManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this);
@ -295,21 +300,9 @@ public class Player implements PlayerHook, FieldFetch {
this.codex = new PlayerCodex(this);
this.applyProperties();
this.applyStartingSceneTags();
this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001);
this.mapMarksManager = new MapMarksManager(this);
this.staminaManager = new StaminaManager(this);
this.sotsManager = new SotSManager(this);
this.energyManager = new EnergyManager(this);
this.resinManager = new ResinManager(this);
this.deforestationManager = new DeforestationManager(this);
this.forgingManager = new ForgingManager(this);
this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this);
}
@Override
@ -587,6 +580,20 @@ public class Player implements PlayerHook, FieldFetch {
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.
*
@ -950,6 +957,13 @@ public class Player implements PlayerHook, FieldFetch {
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) {
this.getPersonalLineList().add(personalLineId);
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);
@ -1316,8 +1330,25 @@ public class Player implements PlayerHook, FieldFetch {
this.getTeamManager().setPlayer(this);
}
/**
* Saves this object to the database.
* As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call.
*/
public void save() {
DatabaseHelper.savePlayer(this);
this.deferSave();
}
/**
* Saves this object to the database.
*
* @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call.
*/
public void save(boolean immediate) {
if (immediate) {
DatabaseObject.super.save();
} else {
this.save();
}
}
// Called from tokenrsp
@ -1377,6 +1408,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())) {
this.setSceneId(this.prevScene <= 0 ? 3 : this.prevScene); // if the player in home, make the player go back.
@ -1484,20 +1519,19 @@ public class Player implements PlayerHook, FieldFetch {
this.getProfile().syncWithCharacter(this);
this.getCoopRequests().clear();
this.getEnterHomeRequests().values().forEach(req -> this.expireEnterHomeRequest(req, true));
this.getEnterHomeRequests().values()
.forEach(req -> this.expireEnterHomeRequest(req, true));
this.getEnterHomeRequests().clear();
// Save to db
this.save();
this.save(true);
this.getTeamManager().saveAvatars();
this.getFriendsList().save();
// Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this);
event.call();
new PlayerQuitEvent(this).call();
} catch (Throwable e) {
e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
Grasscutter.getLogger().warn("Player (UID {}) failed to save.", this.getUid(), e);
} finally {
removeFromServer();
}
@ -1507,7 +1541,8 @@ public class Player implements PlayerHook, FieldFetch {
// Remove from server.
// Note: DON'T DELETE BY UID, BECAUSE THERE ARE MULTIPLE SAME UID PLAYERS WHEN DUPLICATED LOGIN!
//s o I decide to delete by object rather than uid
getServer().getPlayers().values().removeIf(player1 -> player1 == this);
this.getServer().getPlayers().values()
.removeIf(player1 -> player1 == this);
}
public int getLegendaryKey() {

View File

@ -12,6 +12,7 @@ import emu.grasscutter.game.quest.enums.*;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.*;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@ -313,4 +314,28 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
player.save();
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);
}
}

View File

@ -1,11 +1,10 @@
package emu.grasscutter.game.player;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import dev.morphia.annotations.*;
import emu.grasscutter.*;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.database.Database;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.props.*;
@ -23,9 +22,12 @@ import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.*;
import java.util.*;
import java.util.stream.Stream;
import lombok.*;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Entity
public final class TeamManager extends BasePlayerDataManager {
@ -404,7 +406,7 @@ public final class TeamManager extends BasePlayerDataManager {
// Unload removed entities
for (var entity : existingAvatars.values()) {
this.getPlayer().getScene().removeEntity(entity);
entity.getAvatar().save();
entity.getAvatar().save(true);
}
// Set new selected character index
@ -798,10 +800,7 @@ public final class TeamManager extends BasePlayerDataManager {
public void onAvatarDie(long dieGuid) {
EntityAvatar deadAvatar = this.getCurrentAvatarEntity();
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
return;
}
if (deadAvatar == null || deadAvatar.getId() != dieGuid) return;
PlayerDieType dieType = deadAvatar.getKilledType();
int killedBy = deadAvatar.getKilledBy();
@ -965,11 +964,13 @@ public final class TeamManager extends BasePlayerDataManager {
return respawnPoint.get().getPointData().getTranPos();
}
/**
* Performs a bulk save operation on all avatars.
*/
public void saveAvatars() {
// Save all avatars from active team
for (EntityAvatar entity : this.getActiveTeam()) {
entity.getAvatar().save();
}
Database.saveAll(this.getActiveTeam().stream()
.map(EntityAvatar::getAvatar)
.toList());
}
public void onPlayerLogin() { // Hack for now to fix resonances on login

View File

@ -177,7 +177,8 @@ public enum ActionReason {
ChannellerSlabLoopDungeonFirstPassReward(1090),
ChannellerSlabLoopDungeonScoreReward(1091),
HomeLimitedShopBuy(1092),
HomeCoinCollect(1093);
HomeCoinCollect(1093),
HomeAvatarEventReward(1100);
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ActionReason> stringMap = new HashMap<>();

View File

@ -16,11 +16,10 @@ import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.*;
import lombok.*;
import org.bson.types.ObjectId;
import java.util.*;
import java.util.stream.Collectors;
import lombok.*;
import org.bson.types.ObjectId;
@Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest {
@ -170,23 +169,6 @@ public class GameMainQuest {
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 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
* and rotation to the list.
* Checks if the quest has a teleport position. Returns true if it does and adds the target
* position and rotation to the list.
*
* @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.
*/
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) {
try {
List<GameQuest> subQuestsWithCond =
@ -437,7 +380,7 @@ public class GameMainQuest {
for (GameQuest subQuestWithCond : subQuestsWithCond) {
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);
if (condition.getType() == condType) {
boolean result =
@ -445,7 +388,7 @@ public class GameMainQuest {
.getServer()
.getQuestSystem()
.triggerContent(subQuestWithCond, condition, paramStr, params);
subQuestWithCond.getFailProgressList()[i] = result ? 1 : 0;
subQuestWithCond.setFailProgress(i, result ? 1 : 0);
if (result) {
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
}

View File

@ -158,6 +158,13 @@ public class GameQuest {
public boolean clearProgress(boolean notifyDelete) {
// TODO improve
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) {
for (var condition : questData.getFinishCond()) {
if (condition.getType() == QuestContent.QUEST_CONTENT_LUA_NOTIFY) {

View File

@ -27,6 +27,7 @@ public final class QuestManager extends BasePlayerManager {
@Getter private final Player player;
@Getter private final Int2ObjectMap<GameMainQuest> mainQuests;
@Getter private Int2ObjectMap<int[]> acceptProgressLists;
@Getter private final List<Integer> loggedQuests;
private long lastHourCheck = 0;
@ -53,6 +54,7 @@ public final class QuestManager extends BasePlayerManager {
this.player = player;
this.mainQuests = new Int2ObjectOpenHashMap<>();
this.loggedQuests = new ArrayList<>();
this.acceptProgressLists = new Int2ObjectOpenHashMap<>();
if (DEBUG) {
this.loggedQuests.addAll(
@ -100,21 +102,16 @@ public final class QuestManager extends BasePlayerManager {
* Attempts to add the giving action.
*
* @param givingId The giving action ID.
* @throws IllegalStateException If the giving action is already active.
*/
public void addGiveItemAction(int givingId) throws IllegalStateException {
var progress = this.player.getPlayerProgress();
var givings = progress.getItemGivings();
// Check if the action is already present.
if (givings.containsKey(givingId)) {
throw new IllegalStateException("Giving action " + givingId + " is already active.");
}
// Add the action.
// Check if the action is not present.
if (!givings.containsKey(givingId)) {
givings.put(givingId, ItemGiveRecord.resolve(givingId));
// Save the givings.
player.save();
}
this.sendGivingRecords();
}
@ -485,8 +482,6 @@ public final class QuestManager extends BasePlayerManager {
eventExecutor.submit(() -> triggerEvent(condType, paramStr, params));
}
// QUEST_EXEC are handled directly by each subQuest
public void triggerEvent(QuestCond condType, String paramStr, int... params) {
Grasscutter.getLogger().trace("Trigger Event {}, {}, {}", condType, paramStr, params);
var potentialQuests = GameData.getQuestDataByConditions(condType, params[0], paramStr);
@ -503,15 +498,19 @@ public final class QuestManager extends BasePlayerManager {
return;
}
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++) {
val condition = acceptCond.get(i);
if (condition.getType() == condType) {
boolean result =
questSystem.triggerCondition(owner, questData, condition, paramStr, params);
accept[i] = result ? 1 : 0;
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())) {
Grasscutter.getLogger()
.debug(
@ -523,7 +522,7 @@ public final class QuestManager extends BasePlayerManager {
Arrays.stream(params)
.mapToObj(String::valueOf)
.collect(Collectors.joining(", ")));
for (var i = 0; i < accept.length; i++) {
for (var i = 0; i < acceptCond.size(); i++) {
var condition = acceptCond.get(i);
Grasscutter.getLogger()
.debug(
@ -533,14 +532,13 @@ public final class QuestManager extends BasePlayerManager {
.filter(value -> value > 0)
.mapToObj(String::valueOf)
.collect(Collectors.joining(", ")),
accept[i] == 1 ? "success" : "failure");
acceptProgressLists.get(questData.getId())[i] == 1 ? "success" : "failure");
}
}
if (shouldAccept) {
GameQuest quest = owner.getQuestManager().addQuest(questData);
Grasscutter.getLogger()
.debug("Added quest {} result {}", questData.getSubId(), quest != null);
Grasscutter.getLogger().debug("Added quest {}", questData.getSubId());
}
});
}
@ -572,7 +570,7 @@ public final class QuestManager extends BasePlayerManager {
* @param quest The ID of the quest.
*/
public void checkQuestAlreadyFulfilled(GameQuest quest) {
Grasscutter.getThreadPool()
eventExecutor
.submit(
() -> {
for (var condition : quest.getQuestData().getFinishCond()) {

View File

@ -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]);
}
}

View File

@ -5,10 +5,11 @@ import emu.grasscutter.game.quest.*;
import emu.grasscutter.game.quest.enums.QuestContent;
@QuestValueContent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING)
public final class ContentFinishGivingItem extends BaseContent {
public final class ContentFinishItemGiving extends BaseContent {
@Override
public boolean execute(
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]);
}
}

View File

@ -25,7 +25,7 @@ public enum QuestCond implements QuestTrigger {
QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER(17),
QUEST_COND_SCENE_AREA_UNLOCKED(18), // missing, only NPC groups/talks
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_CURRENT_AVATAR(22), // missing
QUEST_COND_CURRENT_AREA(23), // missing

View File

@ -39,7 +39,7 @@ import emu.grasscutter.server.event.entity.EntityCreationEvent;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.packet.send.*;
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 java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -535,7 +535,17 @@ public class Scene {
"Can not solve monster drop: drop_id = {}, drop_tag = {}. Falling back to legacy drop system.",
monster.getMetaMonster().drop_id,
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);
}
}

View File

@ -40,7 +40,7 @@ public class World implements Iterable<Player> {
@Getter private boolean timeLocked;
private long lastUpdateTime;
@Getter private int tickCount = 0;
@Getter protected int tickCount = 0;
@Getter private boolean isPaused = false;
@Getter private long currentWorldTime;
@ -267,7 +267,7 @@ public class World implements Iterable<Player> {
scene.removePlayer(player);
// Info packet for other players
if (this.getPlayers().size() > 0) {
if (!this.getPlayers().isEmpty()) {
this.updatePlayerInfos(player);
}
@ -440,7 +440,7 @@ public class World implements Iterable<Player> {
}
if (oldScene != null && newScene != oldScene) {
newScene.setPrevScene(oldScene.getId());
newScene.setPrevScenePoint(oldScene.getPrevScenePoint());
oldScene.setDontDestroyWhenEmpty(false);
}

View File

@ -0,0 +1,22 @@
package emu.grasscutter.net;
public interface KcpChannel {
/**
* Event fired when the client connects.
*
* @param tunnel The tunnel.
*/
void onConnected(KcpTunnel tunnel);
/**
* Event fired when the client disconnects.
*/
void onDisconnected();
/**
* Event fired when data is received from the client.
*
* @param bytes The data received.
*/
void onMessage(byte[] bytes);
}

View File

@ -0,0 +1,22 @@
package emu.grasscutter.net;
import java.net.InetSocketAddress;
public interface KcpTunnel {
/**
* @return The address of the client.
*/
InetSocketAddress getAddress();
/**
* Sends bytes to the client.
*
* @param bytes The bytes to send.
*/
void writeData(byte[] bytes);
/**
* Closes the connection.
*/
void close();
}

View File

@ -2,7 +2,6 @@ package emu.grasscutter.net.packet;
import com.google.protobuf.GeneratedMessageV3;
import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead;
import emu.grasscutter.utils.Crypto;
import java.io.*;
public class BasePacket {
@ -108,13 +107,7 @@ public class BasePacket {
this.writeBytes(baos, data);
this.writeUint16(baos, const2);
byte[] packet = baos.toByteArray();
if (this.shouldEncrypt) {
Crypto.xor(packet, this.useDispatchKey() ? Crypto.DISPATCH_KEY : Crypto.ENCRYPT_KEY);
}
return packet;
return baos.toByteArray();
}
public void writeUint16(ByteArrayOutputStream baos, int i) {

View File

@ -1,18 +1,17 @@
package emu.grasscutter.plugin;
import static emu.grasscutter.utils.lang.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.*;
import emu.grasscutter.utils.*;
import lombok.*;
import javax.annotation.Nullable;
import java.io.*;
import java.lang.reflect.Method;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import static emu.grasscutter.utils.lang.Language.translate;
import javax.annotation.Nullable;
import lombok.*;
/** Manages the server's plugins and the event system. */
public final class PluginManager {

View File

@ -1180,7 +1180,7 @@ public class SceneScriptManager {
Grasscutter.getLogger()
.warn("trying to cancel a timer that's not active {} {}", groupID, source);
return 1;
return 0;
}
// todo use killed monsters instead of spawned entites for check?

View File

@ -1013,22 +1013,22 @@ public class ScriptLib {
}
public int AddSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented AddSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
logger.debug("[LUA] Call AddSceneTag with {}, {}", sceneId, sceneTagId);
getSceneScriptManager().getScene().getHost().getProgressManager().addSceneTag(sceneId, sceneTagId);
return 0;
}
public int DelSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented DelSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
logger.debug("[LUA] Call DelSceneTag with {}, {}", sceneId, sceneTagId);
getSceneScriptManager().getScene().getHost().getProgressManager().delSceneTag(sceneId, sceneTagId);
return 0;
}
public boolean CheckSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented CheckSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
return false;
logger.debug("[LUA] Call CheckSceneTag with {}, {}", sceneId, sceneTagId);
return getSceneScriptManager().getScene().getHost().getProgressManager().checkSceneTag(sceneId, sceneTagId);
}
public int StartHomeGallery(int galleryId, int uid){
logger.warn("[LUA] Call unimplemented StartHomeGallery with {} {}", galleryId, uid);
//TODO implement
@ -1105,6 +1105,12 @@ public class ScriptLib {
return 0;
}
public int MoveAvatarByPointArrayWithTemplate(int uid, int pointarray_id, int[] routelist, int var4, LuaTable var5){
logger.warn("[LUA] Call unimplemented MoveAvatarByPointArrayWithTemplate with {} {} {} {} {}", uid, pointarray_id, routelist, var4, printTable(var5));
//TODO implement var5 contains int speed
return 0;
}
public int MovePlayerToPos(LuaTable var1){
logger.warn("[LUA] Call unchecked MovePlayerToPos with {}", printTable(var1));
//TODO implement var1 contains int[] uid_list, Position pos, int radius, Position rot
@ -1262,6 +1268,7 @@ public class ScriptLib {
}
public int SetWeatherAreaState(int var1, int var2) {
logger.debug("[LUA] Call SetWeatherAreaState with {} {}", var1, var2);
this.getSceneScriptManager().getScene().getPlayers()
.forEach(p -> p.setWeather(var1, ClimateType.getTypeByValue(var2)));
return 0;
@ -1323,9 +1330,8 @@ public class ScriptLib {
return -1;
}
//TODO check
public int SetPlatformRouteId(int entityConfigId, int routeId){
logger.info("[LUA] Call SetPlatformRouteId {} {}", entityConfigId, routeId);
logger.debug("[LUA] Call SetPlatformRouteId {} {}", entityConfigId, routeId);
val entity = getSceneScriptManager().getScene().getEntityByConfigId(entityConfigId);
if(entity == null){
@ -1358,12 +1364,9 @@ public class ScriptLib {
return 0;
}
//TODO check
public int StartPlatform(int configId){
logger.debug("[LUA] Call StartPlatform {} ", configId);
val entity = sceneScriptManager.get().getScene().getEntityByConfigId(configId);
if(!(entity instanceof EntityGadget entityGadget)) {
return 1;
}
@ -1371,9 +1374,8 @@ public class ScriptLib {
return entityGadget.startPlatform() ? 0 : 2;
}
//TODO check
public int StopPlatform(int configId){
logger.info("[LUA] Call StopPlatform {} ", configId);
logger.debug("[LUA] Call StopPlatform {} ", configId);
val entity = sceneScriptManager.get().getScene().getEntityByConfigId(configId);
if(!(entity instanceof EntityGadget entityGadget)) {
return 1;
@ -1405,6 +1407,11 @@ public class ScriptLib {
return 0;
}
public int SetPlayerInteractOption(String var1){
logger.warn("[LUA] Call unimplemented SetPlayerInteractOption {}", var1);
return 0;
}
public int UnlockForce(int force){
logger.debug("[LUA] Call UnlockForce {}", force);
getSceneScriptManager().getScene().unlockForce(force);
@ -1552,7 +1559,7 @@ public class ScriptLib {
}
public int GetRegionConfigId(LuaTable var1){
logger.warn("[LUA] Call untested GetRegionConfigId with {}", printTable(var1));
logger.debug("[LUA] Call GetRegionConfigId with {}", printTable(var1));
var EntityId = var1.get("region_eid").toint();
var entity = getSceneScriptManager().getScene().getScriptManager().getRegionById(EntityId);
if (entity == null){
@ -1657,9 +1664,11 @@ public class ScriptLib {
}
public int SetGadgetEnableInteract(int groupId, int configId, boolean enable) {
EntityGadget gadget = getCurrentEntityGadget();
if(gadget.getGroupId() != groupId || gadget.getConfigId() != configId) return -1;
logger.debug("[LUA] Call SetGadgetEnableInteract with {} {} {}", groupId, configId, enable);
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId, groupId);
if (!(entity instanceof EntityGadget gadget)) {
return -1;
}
gadget.setInteractEnabled(enable);
return 0;

View File

@ -7,6 +7,7 @@ import lombok.*;
public class SceneGadget extends SceneObject {
public int gadget_id;
public int chest_drop_id;
public int drop_id;
public int drop_count;
public String drop_tag;
boolean showcutscene;

View File

@ -30,16 +30,47 @@ public class SceneRegion {
public boolean contains(Position position) {
switch (shape) {
case ScriptRegionShape.CUBIC:
case ScriptRegionShape.SPHERE -> {
val x = pos.getX() - position.getX();
val y = pos.getY() - position.getY();
val z = pos.getZ() - position.getZ();
// x^2 + y^2 + z^2 = radius^2
return x * x + y * y + z * z <= radius * radius;
}
case ScriptRegionShape.CUBIC -> {
return (Math.abs(pos.getX() - position.getX()) <= size.getX() / 2f)
&& (Math.abs(pos.getY() - position.getY()) <= size.getY() / 2f)
&& (Math.abs(pos.getZ() - position.getZ()) <= size.getZ() / 2f);
case ScriptRegionShape.SPHERE:
var x = Math.pow(pos.getX() - position.getX(), 2);
var y = Math.pow(pos.getY() - position.getY(), 2);
var z = Math.pow(pos.getZ() - position.getZ(), 2);
// ^ means XOR in java!
return x + y + z <= (radius * radius);
}
case ScriptRegionShape.POLYGON -> {
// algorithm is "ray casting": https://www.youtube.com/watch?v=RSXM9bgqxJM
if (Math.abs(pos.getY() - position.getY()) > height / 2f) return false;
var count = 0;
for (var i = 0; i < point_array.size(); ++i) {
val j = (i + 1) % point_array.size();
val yp = position.getZ();
val y1 = point_array.get(i).getY();
val y2 = point_array.get(j).getY();
val xp = position.getX();
val x1 = point_array.get(i).getX();
val x2 = point_array.get(j).getX();
if ((yp < y1) != (yp < y2) && xp < x1 + ((yp - y1) / (y2 - y1)) * (x2 - x1)) {
++count;
}
}
return count % 2 == 1;
}
case ScriptRegionShape.CYLINDER -> {
if (Math.abs(pos.getY() - position.getY()) > height / 2f) return false;
val x = pos.getX() - position.getX();
val z = pos.getZ() - position.getZ();
// x^2 + z^2 = radius^2
return x * x + z * z <= radius * radius;
}
}
return false;
}

View File

@ -1,55 +1,49 @@
package emu.grasscutter.server.game;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.lang.Language.translate;
import emu.grasscutter.*;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassSystem;
import emu.grasscutter.game.chat.ChatSystem;
import emu.grasscutter.game.chat.ChatSystemHandler;
import emu.grasscutter.game.chat.*;
import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.DropSystem;
import emu.grasscutter.game.drop.DropSystemLegacy;
import emu.grasscutter.game.drop.*;
import emu.grasscutter.game.dungeons.DungeonSystem;
import emu.grasscutter.game.expedition.ExpeditionSystem;
import emu.grasscutter.game.gacha.GachaSystem;
import emu.grasscutter.game.home.HomeWorld;
import emu.grasscutter.game.home.HomeWorldMPSystem;
import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingManager;
import emu.grasscutter.game.home.*;
import emu.grasscutter.game.managers.cooking.*;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestSystem;
import emu.grasscutter.game.shop.ShopSystem;
import emu.grasscutter.game.systems.AnnouncementSystem;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.game.systems.MultiplayerSystem;
import emu.grasscutter.game.systems.*;
import emu.grasscutter.game.talk.TalkSystem;
import emu.grasscutter.game.tower.TowerSystem;
import emu.grasscutter.game.world.World;
import emu.grasscutter.game.world.WorldDataSystem;
import emu.grasscutter.game.world.*;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.server.dispatch.DispatchClient;
import emu.grasscutter.server.event.game.ServerTickEvent;
import emu.grasscutter.server.event.internal.ServerStartEvent;
import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.server.event.internal.*;
import emu.grasscutter.server.event.types.ServerEvent;
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.task.TaskMap;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import kcp.highway.*;
import lombok.*;
import org.jetbrains.annotations.*;
import emu.grasscutter.server.game.session.GameSessionManager;
import java.net.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import kcp.highway.*;
import lombok.*;
import org.jetbrains.annotations.*;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.lang.Language.translate;
@Getter
public final class GameServer extends KcpServer implements Iterable<Player> {
@ -60,6 +54,7 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
private final Set<World> worlds;
private final Int2ObjectMap<HomeWorld> homeWorlds;
@Getter private boolean started = false;
@Setter private DispatchClient dispatchClient;
// Server systems
@ -140,7 +135,7 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
channelConfig.setUseConvChannel(true);
channelConfig.setAckNoDelay(false);
this.init(GameSessionManager.getListener(), channelConfig, address);
this.init(GameSessionManager.getInstance(), channelConfig, address);
EnergyManager.initialize();
StaminaManager.initialize();
@ -288,12 +283,9 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
public synchronized void onTick() {
var tickStart = Instant.now();
// Tick worlds.
// Tick worlds and home worlds.
this.worlds.removeIf(World::onTick);
// Tick Home Worlds (Not remove, HomeWorld is constant).
this.homeWorlds.values().forEach(HomeWorld::onTick);
// Tick players.
this.players.values().forEach(Player::onTick);
@ -350,6 +342,8 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
.info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort()));
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
event.call();
this.started = true;
}
public void onServerShutdown() {
@ -364,10 +358,10 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
this.stop(); // Stop the server.
try {
var threadPool = GameSessionManager.getLogicThread();
var threadPool = GameSessionManager.getExecutor();
// Shutdown network thread.
threadPool.shutdownGracefully();
threadPool.shutdown();
// Wait for the network thread to finish.
if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
Grasscutter.getLogger().error("Logic thread did not terminate!");

View File

@ -1,7 +1,5 @@
package emu.grasscutter.server.game;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.net.packet.*;
@ -9,6 +7,8 @@ import emu.grasscutter.server.event.game.ReceivePacketEvent;
import emu.grasscutter.server.game.GameSession.SessionState;
import it.unimi.dsi.fastutil.ints.*;
import static emu.grasscutter.config.Configuration.GAME_INFO;
public final class GameServerPacketHandler {
private final Int2ObjectMap<PacketHandler> handlers;
@ -76,13 +76,11 @@ public final class GameServerPacketHandler {
}
// Invoke event.
ReceivePacketEvent event = new ReceivePacketEvent(session, opcode, payload);
event.call();
if (!event.isCanceled()) // If event is not canceled, continue.
var event = new ReceivePacketEvent(session, opcode, payload);
if (event.call()) // If event is not canceled, continue.
handler.handle(session, header, event.getPacketData());
} catch (Exception ex) {
// TODO Remove this when no more needed
ex.printStackTrace();
Grasscutter.getLogger().warn("Unable to handle packet.", ex);
}
return; // Packet successfully handled
}

View File

@ -1,28 +1,33 @@
package emu.grasscutter.server.game;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.lang.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.*;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.server.event.game.SendPacketEvent;
import emu.grasscutter.utils.*;
import io.netty.buffer.*;
import lombok.*;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import lombok.*;
public class GameSession implements GameSessionManager.KcpChannel {
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.lang.Language.translate;
public class GameSession implements KcpChannel {
private final GameServer server;
private GameSessionManager.KcpTunnel tunnel;
private KcpTunnel tunnel;
@Getter @Setter private Account account;
@Getter private Player player;
@Getter private long encryptSeed = Crypto.ENCRYPT_SEED;
private byte[] encryptKey = Crypto.ENCRYPT_KEY;
@Setter private boolean useSecretKey;
@Getter @Setter private SessionState state;
@ -34,6 +39,11 @@ public class GameSession implements GameSessionManager.KcpChannel {
this.server = server;
this.state = SessionState.WAITING_FOR_TOKEN;
this.lastPingTime = System.currentTimeMillis();
if (GAME_INFO.useUniquePacketKey) {
this.encryptKey = new byte[4096];
this.encryptSeed = Crypto.generateEncryptKeyAndSeed(this.encryptKey);
}
}
public GameServer getServer() {
@ -133,7 +143,12 @@ public class GameSession implements GameSessionManager.KcpChannel {
event.call();
if (!event.isCanceled()) { // If event is not cancelled, continue.
try {
tunnel.writeData(event.getPacket().build());
packet = event.getPacket();
var bytes = packet.build();
if (packet.shouldEncrypt) {
Crypto.xor(bytes, packet.useDispatchKey() ? Crypto.DISPATCH_KEY : this.encryptKey);
}
this.tunnel.writeData(bytes);
} catch (Exception ignored) {
Grasscutter.getLogger().debug("Unable to send packet to client.");
}
@ -141,15 +156,15 @@ public class GameSession implements GameSessionManager.KcpChannel {
}
@Override
public void onConnected(GameSessionManager.KcpTunnel tunnel) {
public void onConnected(KcpTunnel tunnel) {
this.tunnel = tunnel;
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
}
@Override
public void handleReceive(byte[] bytes) {
public void onMessage(byte[] bytes) {
// Decrypt and turn back into a packet
Crypto.xor(bytes, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
Crypto.xor(bytes, useSecretKey() ? this.encryptKey : Crypto.DISPATCH_KEY);
ByteBuf packet = Unpooled.wrappedBuffer(bytes);
// Log
@ -213,8 +228,8 @@ public class GameSession implements GameSessionManager.KcpChannel {
// Handle
getServer().getPacketHandler().handle(this, opcode, header, payload);
}
} catch (Exception e) {
e.printStackTrace();
} catch (Exception exception) {
Grasscutter.getLogger().warn("Unable to handle packet.", exception);
} finally {
// byteBuf.release(); //Needn't
packet.release();
@ -222,8 +237,9 @@ public class GameSession implements GameSessionManager.KcpChannel {
}
@Override
public void handleClose() {
public void onDisconnected() {
setState(SessionState.INACTIVE);
// send disconnection pack in case of reconnection
Grasscutter.getLogger()
.info(translate("messages.game.disconnect", this.getAddress().toString()));

View File

@ -1,114 +0,0 @@
package emu.grasscutter.server.game;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.Utils;
import io.netty.buffer.*;
import io.netty.channel.DefaultEventLoop;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import kcp.highway.*;
import lombok.Getter;
public class GameSessionManager {
@Getter private static final DefaultEventLoop logicThread = new DefaultEventLoop();
private static final ConcurrentHashMap<Ukcp, GameSession> sessions = new ConcurrentHashMap<>();
private static final KcpListener listener =
new KcpListener() {
@Override
public void onConnected(Ukcp ukcp) {
int times = 0;
GameServer server = Grasscutter.getGameServer();
while (server == null) { // Waiting server to establish
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
ukcp.close();
return;
}
if (times++ > 5) {
Grasscutter.getLogger().error("Service is not available!");
ukcp.close();
return;
}
server = Grasscutter.getGameServer();
}
GameSession conversation = new GameSession(server);
conversation.onConnected(
new KcpTunnel() {
@Override
public InetSocketAddress getAddress() {
return ukcp.user().getRemoteAddress();
}
@Override
public void writeData(byte[] bytes) {
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
ukcp.write(buf);
buf.release();
}
@Override
public void close() {
ukcp.close();
}
@Override
public int getSrtt() {
return ukcp.srtt();
}
});
sessions.put(ukcp, conversation);
}
@Override
public void handleReceive(ByteBuf buf, Ukcp kcp) {
var byteData = Utils.byteBufToArray(buf);
logicThread.execute(
() -> {
try {
var conversation = sessions.get(kcp);
if (conversation != null) {
conversation.handleReceive(byteData);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
@Override
public void handleException(Throwable ex, Ukcp ukcp) {}
@Override
public void handleClose(Ukcp ukcp) {
GameSession conversation = sessions.get(ukcp);
if (conversation != null) {
conversation.handleClose();
sessions.remove(ukcp);
}
}
};
public static KcpListener getListener() {
return listener;
}
public interface KcpTunnel {
InetSocketAddress getAddress();
void writeData(byte[] bytes);
void close();
int getSrtt();
}
interface KcpChannel {
void onConnected(KcpTunnel tunnel);
void handleClose();
void handleReceive(byte[] bytes);
}
}

View File

@ -0,0 +1,31 @@
package emu.grasscutter.server.game.session;
import emu.grasscutter.net.KcpTunnel;
import io.netty.buffer.Unpooled;
import kcp.highway.Ukcp;
import lombok.*;
import java.net.InetSocketAddress;
@RequiredArgsConstructor
public final class GameSessionHandler implements KcpTunnel {
@Getter private final Ukcp handle;
@Override
public InetSocketAddress getAddress() {
return this.getHandle().user().getRemoteAddress();
}
@Override
public void writeData(byte[] bytes) {
var buffer = Unpooled.wrappedBuffer(bytes);
this.getHandle().write(buffer);
buffer.release();
}
@Override
public void close() {
this.getHandle().close();
}
}

View File

@ -0,0 +1,89 @@
package emu.grasscutter.server.game.session;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.game.*;
import emu.grasscutter.utils.Utils;
import io.netty.buffer.ByteBuf;
import kcp.highway.*;
import lombok.Getter;
import java.util.Map;
import java.util.concurrent.*;
public final class GameSessionManager implements KcpListener {
@Getter private static final GameSessionManager instance
= new GameSessionManager();
@Getter private static final ExecutorService executor
= Executors.newWorkStealingPool();
@Getter private static final Map<Ukcp, GameSession> sessions
= new ConcurrentHashMap<>();
/**
* Waits for the game server to be ready.
*
* @return The game server.
*/
private GameServer waitForServer() {
var server = Grasscutter.getGameServer();
var times = 0; while (server == null || !server.isStarted()) {
Utils.sleep(1000); // Wait 1s for the server to start.
if (times++ > 5) {
Grasscutter.getLogger().error("Game server has not started in a reasonable time.");
return null;
}
server = Grasscutter.getGameServer();
}
return server;
}
@Override
public void onConnected(Ukcp ukcp) {
// Fetch the game server.
var server = this.waitForServer();
if (server == null) {
ukcp.close();
return;
}
// Create a new session.
var session = sessions.compute(ukcp, (k, existing) -> {
// Close an existing session.
if (existing != null) {
existing.close();
}
return new GameSession(server);
});
// Connect the session.
session.onConnected(new GameSessionHandler(ukcp));
}
@Override
public void handleReceive(ByteBuf byteBuf, Ukcp ukcp) {
// Get the session.
var session = sessions.get(ukcp);
if (session == null) {
ukcp.close(); return;
}
// Handle the message in a separate thread.
var bytes = Utils.byteBufToArray(byteBuf);
executor.submit(() -> session.onMessage(bytes));
}
@Override
public void handleException(Throwable throwable, Ukcp ukcp) {
Grasscutter.getLogger().error("Exception in game session.", throwable);
}
@Override
public void handleClose(Ukcp ukcp) {
var session = sessions.remove(ukcp);
if (session != null) {
session.close();
}
}
}

View File

@ -1,5 +1,7 @@
package emu.grasscutter.server.http.dispatch;
import static emu.grasscutter.config.Configuration.*;
import com.google.gson.*;
import com.google.protobuf.ByteString;
import emu.grasscutter.*;
@ -16,14 +18,11 @@ import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import emu.grasscutter.utils.*;
import io.javalin.Javalin;
import io.javalin.http.Context;
import org.slf4j.Logger;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import static emu.grasscutter.config.Configuration.*;
import org.slf4j.Logger;
/** Handles requests related to region queries. */
public final class RegionHandler implements Router {

View File

@ -0,0 +1,49 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ClientScriptEventNotifyOuterClass.ClientScriptEventNotify;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.game.GameSession;
import lombok.val;
@Opcodes(PacketOpcodes.ClientScriptEventNotify)
public class HandlerClientScriptEventNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
val data = ClientScriptEventNotify.parseFrom(payload);
val scriptManager = session.getPlayer().getScene().getScriptManager();
val args =
new ScriptArgs(0, data.getEventType())
.setSourceEntityId(data.getSourceEntityId())
.setTargetEntityId(data.getTargetEntityId());
for (int i = 0; i < data.getParamListCount(); i++) {
switch (i) {
case 0 -> args.setParam1(data.getParamList(i));
case 1 -> args.setParam2(data.getParamList(i));
case 2 -> args.setParam3(data.getParamList(i));
}
}
if (data.getEventType() == EventType.EVENT_AVATAR_NEAR_PLATFORM) {
if (data.getParamList(0) == 0) {
Grasscutter.getLogger()
.debug(
"Found a zero Param1 for an EVENT_AVATAR_NEAR_PLATFORM. Doing the configID workaround.");
val entity = session.getPlayer().getScene().getEntityById(data.getSourceEntityId());
if (entity == null) {
Grasscutter.getLogger().debug("But it failed.");
} else {
args.setParam1(entity.getConfigId());
}
}
}
scriptManager.callEvent(args);
}
}

View File

@ -111,32 +111,32 @@ public class HandlerGetPlayerTokenReq extends PacketHandler {
// Only >= 2.7.50 has this
if (req.getKeyId() > 0) {
var encryptSeed = session.getEncryptSeed();
try {
var cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, Crypto.CUR_SIGNING_KEY);
var client_seed_encrypted = Utils.base64Decode(req.getClientRandKey());
var client_seed = ByteBuffer.wrap(cipher.doFinal(client_seed_encrypted)).getLong();
var clientSeedEncrypted = Utils.base64Decode(req.getClientRandKey());
var clientSeed = ByteBuffer.wrap(cipher.doFinal(clientSeedEncrypted)).getLong();
var seed_bytes =
ByteBuffer.wrap(new byte[8]).putLong(Crypto.ENCRYPT_SEED ^ client_seed).array();
var seedBytes = ByteBuffer.wrap(new byte[8]).putLong(encryptSeed ^ clientSeed).array();
cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(req.getKeyId()));
var seed_encrypted = cipher.doFinal(seed_bytes);
var seedEncrypted = cipher.doFinal(seedBytes);
var privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(Crypto.CUR_SIGNING_KEY);
privateSignature.update(seed_bytes);
privateSignature.update(seedBytes);
session.send(
new PacketGetPlayerTokenRsp(
session,
Utils.base64Encode(seed_encrypted),
Utils.base64Encode(seedEncrypted),
Utils.base64Encode(privateSignature.sign())));
} catch (Exception ignored) {
// Only UA Patch users will have exception
var clientBytes = Utils.base64Decode(req.getClientRandKey());
var seed = ByteHelper.longToBytes(Crypto.ENCRYPT_SEED);
var seed = ByteHelper.longToBytes(encryptSeed);
Crypto.xor(clientBytes, seed);
var base64str = Utils.base64Encode(clientBytes);

View File

@ -0,0 +1,29 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.HomeAvatarRewardEventGetReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeAvatarAllFinishRewardNotify;
import emu.grasscutter.server.packet.send.PacketHomeAvatarRewardEventGetRsp;
import emu.grasscutter.server.packet.send.PacketHomeAvatarRewardEventNotify;
@Opcodes(PacketOpcodes.HomeAvatarRewardEventGetReq)
public class HandlerHomeAvatarRewardEventGetReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = HomeAvatarRewardEventGetReqOuterClass.HomeAvatarRewardEventGetReq.parseFrom(payload);
var player = session.getPlayer();
var rewardsOrError =
player.getCurHomeWorld().getModuleManager().claimAvatarRewards(req.getEventId());
session.send(new PacketHomeAvatarRewardEventNotify(player));
session.send(new PacketHomeAvatarAllFinishRewardNotify(player));
session.send(
rewardsOrError.map(
gameItems -> new PacketHomeAvatarRewardEventGetRsp(req.getEventId(), gameItems),
integer -> new PacketHomeAvatarRewardEventGetRsp(req.getEventId(), integer)));
}
}

View File

@ -0,0 +1,22 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.HomeAvatarSummonEventReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonEventRsp;
@Opcodes(PacketOpcodes.HomeAvatarSummonEventReq)
public class HandlerHomeAvatarSummonEventReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = HomeAvatarSummonEventReqOuterClass.HomeAvatarSummonEventReq.parseFrom(payload);
var moduleManager = session.getPlayer().getCurHomeWorld().getModuleManager();
var eventOrError =
moduleManager.fireAvatarSummonEvent(
session.getPlayer(), req.getAvatarId(), req.getGuid(), req.getSuitId());
session.send(
eventOrError.map(PacketHomeAvatarSummonEventRsp::new, PacketHomeAvatarSummonEventRsp::new));
}
}

View File

@ -0,0 +1,21 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.HomeAvatarSummonFinishReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonAllEventNotify;
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonFinishRsp;
@Opcodes(PacketOpcodes.HomeAvatarSummonFinishReq)
public class HandlerHomeAvatarSummonFinishReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = HomeAvatarSummonFinishReqOuterClass.HomeAvatarSummonFinishReq.parseFrom(payload);
var player = session.getPlayer();
player.getCurHomeWorld().getModuleManager().onFinishSummonEvent(req.getEventId());
session.send(new PacketHomeAvatarSummonAllEventNotify(session.getPlayer()));
session.send(new PacketHomeAvatarSummonFinishRsp(req.getEventId()));
}
}

View File

@ -1,5 +1,6 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.home.HomeScene;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
@ -31,14 +32,8 @@ public class HandlerHomeChangeEditModeReq extends PacketHandler {
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
if (!req.getIsEnterEditMode()) {
var scene = session.getPlayer().getScene();
scene.addEntities(
session
.getPlayer()
.getCurHomeWorld()
.getHome()
.getHomeSceneItem(scene.getId())
.getAnimals(scene));
var scene = (HomeScene) session.getPlayer().getScene();
scene.onLeaveEditMode();
}
session.send(new PacketHomeChangeEditModeRsp(req.getIsEnterEditMode()));

View File

@ -1,7 +1,5 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
@ -21,7 +19,8 @@ public class HandlerHomeChangeModuleReq extends PacketHandler {
HomeChangeModuleReqOuterClass.HomeChangeModuleReq req =
HomeChangeModuleReqOuterClass.HomeChangeModuleReq.parseFrom(payload);
if (!session.getPlayer().getCurHomeWorld().getGuests().isEmpty()) {
var homeWorld = session.getPlayer().getCurHomeWorld();
if (!homeWorld.getGuests().isEmpty()) {
session.send(new PacketHomeChangeModuleRsp());
return;
}
@ -33,13 +32,10 @@ public class HandlerHomeChangeModuleReq extends PacketHandler {
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
int realmId = 2000 + req.getTargetModuleId();
var scene = homeWorld.getSceneById(realmId);
var pos = scene.getScriptManager().getConfig().born_pos;
Scene scene = session.getPlayer().getWorld().getSceneById(realmId);
Position pos = scene.getScriptManager().getConfig().born_pos;
session
.getPlayer()
.getWorld()
.transferPlayerToScene(session.getPlayer(), realmId, TeleportType.WAYPOINT, pos);
homeWorld.transferPlayerToScene(session.getPlayer(), realmId, TeleportType.WAYPOINT, pos);
homeWorld.refreshModuleManager();
}
}

View File

@ -14,6 +14,7 @@ public class HandlerHomeChooseModuleReq extends PacketHandler {
HomeChooseModuleReqOuterClass.HomeChooseModuleReq.parseFrom(payload);
session.getPlayer().addRealmList(req.getModuleId());
session.getPlayer().setCurrentRealmId(req.getModuleId());
session.getPlayer().getCurHomeWorld().refreshModuleManager();
session.send(new PacketHomeChooseModuleRsp(req.getModuleId()));
session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer()));
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));

View File

@ -1,10 +1,9 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.game.home.HomeScene;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeEnterEditModeFinishRsp;
@ -17,12 +16,8 @@ public class HandlerHomeEnterEditModeFinishReq extends PacketHandler {
* This packet is about the edit mode
*/
var scene = session.getPlayer().getScene();
scene.removeEntities(
scene.getEntities().values().stream()
.filter(gameEntity -> gameEntity instanceof EntityHomeAnimal)
.toList(),
VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
var scene = (HomeScene) session.getPlayer().getScene();
scene.onEnterEditModeFinish();
session.send(new PacketHomeEnterEditModeFinishRsp());
}

View File

@ -1,6 +1,8 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.OtherPlayerEnterHomeNotifyOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
@ -26,6 +28,12 @@ public class HandlerHomeSceneInitFinishReq extends PacketHandler {
}
}
curHomeWorld.ifHost(
session.getPlayer(),
player -> {
player.sendPacket(new PacketHomeAvatarRewardEventNotify(player));
player.sendPacket(new PacketHomeAvatarSummonAllEventNotify(player));
});
session.send(new PacketHomeMarkPointNotify(session.getPlayer()));
session.send(new PacketHomeSceneInitFinishRsp());

View File

@ -5,10 +5,7 @@ import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.HomeUpdateArrangementInfoReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeAvatarTalkFinishInfoNotify;
import emu.grasscutter.server.packet.send.PacketHomeBasicInfoNotify;
import emu.grasscutter.server.packet.send.PacketHomeMarkPointNotify;
import emu.grasscutter.server.packet.send.PacketHomeUpdateArrangementInfoRsp;
import emu.grasscutter.server.packet.send.*;
@Opcodes(PacketOpcodes.HomeUpdateArrangementInfoReq)
public class HandlerHomeUpdateArrangementInfoReq extends PacketHandler {
@ -22,14 +19,17 @@ public class HandlerHomeUpdateArrangementInfoReq extends PacketHandler {
session.getPlayer().getHome().getHomeSceneItem(session.getPlayer().getSceneId());
var roomSceneId = homeScene.getRoomSceneId();
homeScene.update(req.getSceneArrangementInfo());
homeScene.update(req.getSceneArrangementInfo(), session.getPlayer());
if (roomSceneId != homeScene.getRoomSceneId()) {
session.getPlayer().getHome().onMainHouseChanged();
}
session.getPlayer().getCurHomeWorld().getModuleManager().onUpdateArrangement();
session.send(new PacketHomeAvatarRewardEventNotify(session.getPlayer()));
session.send(
new PacketHomeBasicInfoNotify(session.getPlayer(), session.getPlayer().isInEditMode()));
session.send(new PacketHomeAvatarTalkFinishInfoNotify(session.getPlayer()));
session.send(new PacketHomeAvatarSummonAllEventNotify(session.getPlayer()));
session.send(new PacketHomeMarkPointNotify(session.getPlayer()));
session.getPlayer().getHome().save();

View File

@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.net.proto.ItemGivingReqOuterClass.ItemGivingReq;
@ -50,10 +51,11 @@ public final class HandlerItemGivingReq extends PacketHandler {
player.sendPacket(new PacketItemGivingRsp(giveId, Mode.EXACT_SUCCESS));
// Remove the action from the active givings.
questManager.removeGivingItemAction(giveId);
// Queue the content action.
// Queue the content and condition actions.
questManager.queueEvent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING, giveId, 0);
questManager.queueEvent(QuestCond.QUEST_COND_ITEM_GIVING_FINISHED, giveId, 0);
}
case GIVING_METHOD_VAGUE_GROUP -> {
case GIVING_METHOD_VAGUE_GROUP, GIVING_METHOD_GROUP -> {
var matchedGroups = new ArrayList<Integer>();
var givenItems = new HashMap<Integer, Integer>();
@ -96,8 +98,11 @@ public final class HandlerItemGivingReq extends PacketHandler {
player.sendPacket(new PacketItemGivingRsp(matchedGroups.get(0), Mode.GROUP_SUCCESS));
// Mark the giving action as completed.
questManager.markCompleted(giveId);
// Queue the content action.
questManager.queueEvent(QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING, giveId, 0);
// Queue the content and condition actions.
questManager.queueEvent(
QuestContent.QUEST_CONTENT_FINISH_ITEM_GIVING, giveId, matchedGroups.get(0));
questManager.queueEvent(
QuestCond.QUEST_COND_ITEM_GIVING_FINISHED, giveId, matchedGroups.get(0));
}
}
}

View File

@ -17,7 +17,7 @@ public class HandlerPlayerEnterDungeonReq extends PacketHandler {
session
.getServer()
.getDungeonSystem()
.enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId());
.enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId(), true);
session
.getPlayer()
.sendPacket(new PacketPlayerEnterDungeonRsp(req.getPointId(), req.getDungeonId(), success));

View File

@ -17,7 +17,7 @@ public class HandlerSceneInitFinishReq extends PacketHandler {
session.send(new PacketServerTimeNotify());
session.send(new PacketWorldPlayerInfoNotify(world));
session.send(new PacketWorldDataNotify(world));
session.send(new PacketPlayerWorldSceneInfoListNotify());
session.send(new PacketPlayerWorldSceneInfoListNotify(player));
session.send(new BasePacket(PacketOpcodes.SceneForceUnlockNotify));
session.send(new PacketHostPlayerNotify(world));

View File

@ -20,7 +20,7 @@ public class PacketGetPlayerTokenRsp extends BasePacket {
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(session.getEncryptSeed())
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
.setPlatformType(3)
.setChannelId(1)
@ -66,7 +66,7 @@ public class PacketGetPlayerTokenRsp extends BasePacket {
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(session.getEncryptSeed())
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
.setPlatformType(3)
.setChannelId(1)

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