mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-07-04 14:03:33 +00:00
Compare commits
26 Commits
v1.7.3
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
f373827a83 | |||
74b8de36d3 | |||
9c36daa3fa | |||
d340758614 | |||
76fd5b2e9c | |||
4022267888 | |||
f1f5b54939 | |||
f871f261e1 | |||
eeaccf32c4 | |||
6e1913aacb | |||
9e17e4aacb | |||
770a793c69 | |||
c4402cc287 | |||
5ebad71e9d | |||
564b609028 | |||
cdb0dc560a | |||
d8c3da8fcd | |||
13c40b53a7 | |||
f1c1a84683 | |||
2bcbd41026 | |||
adf8031684 | |||
0bbeaf254b | |||
1fac319eb2 | |||
d224178a64 | |||
d461ee2eb3 | |||
24874e7fba |
51
.github/workflows/build_container.yml
vendored
Normal file
51
.github/workflows/build_container.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
name: Build Docker Container
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch: ~
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Project
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Docker Meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.1.0
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3.0.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Docker image
|
||||||
|
uses: docker/build-push-action@v5.2.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -64,6 +64,7 @@ tmp/
|
|||||||
|
|
||||||
/*.jar
|
/*.jar
|
||||||
/*.sh
|
/*.sh
|
||||||
|
!entrypoint.sh
|
||||||
|
|
||||||
GM Handbook*.txt
|
GM Handbook*.txt
|
||||||
handbook.html
|
handbook.html
|
||||||
|
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Builder
|
||||||
|
FROM gradle:jdk17-alpine as builder
|
||||||
|
|
||||||
|
RUN apk add --update nodejs npm
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
|
RUN gradle jar --no-daemon
|
||||||
|
|
||||||
|
# Fetch Data
|
||||||
|
FROM bitnami/git:2.43.0-debian-11-r1 as data
|
||||||
|
|
||||||
|
ARG DATA_REPOSITORY=https://gitlab.com/YuukiPS/GC-Resources.git
|
||||||
|
ARG DATA_BRANCH=4.0
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN git clone --branch ${DATA_BRANCH} --depth 1 ${DATA_REPOSITORY}
|
||||||
|
|
||||||
|
# Result Container
|
||||||
|
FROM amazoncorretto:17-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built assets
|
||||||
|
COPY --from=builder /app/grasscutter-*.jar /app/grasscutter.jar
|
||||||
|
COPY --from=builder /app/keystore.p12 /app/keystore.p12
|
||||||
|
|
||||||
|
# Copy the resources
|
||||||
|
COPY --from=data /app/GC-Resources/Resources /app/resources/
|
||||||
|
|
||||||
|
# Copy startup files
|
||||||
|
COPY ./entrypoint.sh /app/
|
||||||
|
|
||||||
|
CMD [ "sh", "/app/entrypoint.sh" ]
|
||||||
|
|
||||||
|
EXPOSE 80 443 8888 22102
|
17
README.md
17
README.md
@ -18,18 +18,27 @@
|
|||||||
* Spawning monsters via console
|
* Spawning monsters via console
|
||||||
* Inventory features (receiving items/characters, upgrading items/characters, etc)
|
* Inventory features (receiving items/characters, upgrading items/characters, etc)
|
||||||
|
|
||||||
|
## Foreward
|
||||||
|
|
||||||
|
### **Grasscutter beyond the latest release will have no handholding in terms of instructions.**
|
||||||
|
|
||||||
|
Grasscutter has not been actively maintained and currently (as of January 12th, 2025) only works up to version REL4.0.1 (introduction to Fontaine). If you have a beta version/unofficial version of Grasscutter, this guide should theoretically still work, however, we will not provide official support these versions. You can still try your luck in the Discord if you are stuck, but please don't act entitled.
|
||||||
|
|
||||||
## Quick setup guide
|
## Quick setup guide
|
||||||
|
|
||||||
**Note**: For support please join our [Discord](https://discord.gg/T5vZU6UyeG).
|
**Note**: For support please join our [Discord](https://discord.gg/T5vZU6UyeG).
|
||||||
|
|
||||||
### Quick Start (automatic)
|
### Quick Start (automatic)
|
||||||
|
|
||||||
- Get Java 17: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
- Get [Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
||||||
- Get [MongoDB Community Server](https://www.mongodb.com/try/download/community)
|
- Get [MongoDB Community Server](https://www.mongodb.com/try/download/community)
|
||||||
- Get game version 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
|
- Get game version REL4.0.x (If you don't have a 4.0.x client, you can find it here and open any of the links to download it):
|
||||||
|
[4.0.x Client-github](https://github.com/JRSKelvin/GenshinRepository/blob/main/Version%204.0.0.md)
|
||||||
|
[4.0.x Client-cloud drive](https://www.123pan.com/s/HoqUVv-U7SBA.html)
|
||||||
|
- ***UPDATE JAN 12, 2025: YOU CANNOT MIX AND MATCH GAME VERSIONS AND SERVER VERSIONS, PLEASE DOWNLOAD THE CORRECT VERSION OF GRASSCUTTER FOR YOUR VERSION OF THE GAME.***
|
||||||
|
|
||||||
- Download the [latest Cultivation version](https://github.com/Grasscutters/Cultivation/releases/latest). Use the `.msi` installer.
|
- Download the [latest Cultivation version](https://github.com/Grasscutters/Cultivation/releases/latest). Use the `.msi` installer.
|
||||||
- After opening Culivation (as admin), press the download button in the upper right corner.
|
- After opening Cultivation (as admin), press the download button in the upper right corner.
|
||||||
- Click `Download All-in-One`
|
- Click `Download All-in-One`
|
||||||
- Click the gear in the upper right corner
|
- Click the gear in the upper right corner
|
||||||
- Set the game Install path to where your game is located.
|
- Set the game Install path to where your game is located.
|
||||||
@ -38,7 +47,7 @@
|
|||||||
|
|
||||||
- Click the small button next to launch.
|
- Click the small button next to launch.
|
||||||
- Click the launch button.
|
- Click the launch button.
|
||||||
- Log in with whatever username you want. Password doesn't matter.
|
- Log in with whatever username you want. Password can be anything.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ sourceCompatibility = JavaVersion.VERSION_17
|
|||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
||||||
group = 'io.grasscutter'
|
group = 'io.grasscutter'
|
||||||
version = '1.7.3'
|
version = '1.7.4'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
withJavadocJar()
|
withJavadocJar()
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
[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)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**Aantekening:** We verwelkomen altijd bijdragers aan het project. Lees onze [Gedragscode](https://github.com/Grasscutters/Grasscutter/blob/development/README_NL.md#bijdragen-aan-het-project) zorgvuldig door voordat u uw bijdrage toevoegt.
|
**Aantekening:** We verwelkomen altijd bijdragers aan het project. Lees onze [Gedragscode](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) zorgvuldig door voordat u uw bijdrage toevoegt.
|
||||||
|
|
||||||
## Huidige functies
|
## Huidige functies
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](README.md) | [简中](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)
|
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.md)
|
||||||
|
|
||||||
**ध्यान:** हम हमेशा परियोजना में योगदानकर्ताओं का स्वागत करते हैं।. अपना योगदान जोड़ने से पहले कृपया हमारा ध्यानपूर्वक पढ़ें [आचार संहिता](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
**ध्यान:** हम हमेशा परियोजना में योगदानकर्ताओं का स्वागत करते हैं।. अपना योगदान जोड़ने से पहले कृपया हमारा ध्यानपूर्वक पढ़ें [आचार संहिता](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
@ -3,81 +3,64 @@
|
|||||||
|
|
||||||
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
|
||||||
|
|
||||||
[EN](../README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md) | [IT](README_it-IT.md) | [VI](README_vi-VN.md) | [हिंदी](README_hn-IN.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) | [HI](README_hn-IN.md)
|
||||||
|
|
||||||
|
|
||||||
***:** 私たちはプロジェクトへの貢献者をいつでも歓迎します。貢献を追加する前に、我々の [行動規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)をよくお読みください。
|
**Attention:** 私たちはプロジェクトへのコントリビュータをいつでも歓迎します。コントリビュートする前に、私たちの [行動規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)をよくお読みください。
|
||||||
|
|
||||||
## 現在機能している物
|
## 現在実装されている機能
|
||||||
|
|
||||||
* ログイン
|
* ログイン
|
||||||
* 戦闘
|
* 戦闘
|
||||||
* フレンドリスト
|
* フレンドリスト
|
||||||
* テレポート
|
* テレポート
|
||||||
* 祈願(ガチャ)
|
* 祈願 (ガチャ)
|
||||||
* マルチプレイは一部機能しています
|
* マルチプレイ (一部)
|
||||||
* コンソールを使用してモンスターをスポーンさせる
|
* コンソールを通したモンスターのスポーン
|
||||||
* インベントリ機能 (アイテム/キャラクターの受け取り、アイテム/キャラクターのアップグレードなど)
|
* インベントリ機能 (アイテム/キャラクターの受け取り、アイテム/キャラクターのアップグレードなど)
|
||||||
|
|
||||||
## クイックセットアップガイド
|
## かんたんセットアップガイド
|
||||||
|
|
||||||
***:** サポートが必要な場合はGrasscutterの[Discord](https://discord.gg/T5vZU6UyeG)に参加してください。
|
**Note:** サポートが必要な場合はGrasscutterの[Discordサーバー](https://discord.gg/T5vZU6UyeG)に参加してください。
|
||||||
|
|
||||||
### 動作環境
|
### パパっとスタートアップ
|
||||||
|
|
||||||
* [JAVAのバージョン17以降](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
- [Java (バージョン17以降)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) を用意する
|
||||||
|
- [MongoDB Community Server](https://www.mongodb.com/try/download/community) を用意する
|
||||||
|
- ゲームバージョンがREL4.0.Xのクライアントを用意する (4.0.Xのクライアントを持っていない場合は右のリンクからダウンロード): [Github](https://github.com/JRSKelvin/GenshinRepository/blob/main/Version%204.0.0.md), [クラウド(123云盘)](https://www.123pan.com/s/HoqUVv-U7SBA.html)
|
||||||
|
- [最新の Cultivation](https://github.com/Grasscutters/Cultivation/releases/latest)をダウンロードする。`.msi`インストーラを使ってください。
|
||||||
|
- 管理者権限を付与して Cultivation を実行した後、右上端にあるダウンロードアイコンのボタンを押す。
|
||||||
|
- `Download All-in-One` をクリックする
|
||||||
|
- 右上端にある歯車アイコンのボタンをクリックする。
|
||||||
|
- `Game Install Path` にゲームファイルのパスを指定する。
|
||||||
|
- `Custom Java Path` に、自分が用意したJavaのパスを指定する。 (例: `C:\Program Files\Java\jdk-17\bin\java.exe`)
|
||||||
|
- その他の設定には手を付けず次の段階に進む。
|
||||||
|
- Launch の隣にある小さいボタンを押す。
|
||||||
|
- Launchボタンを押す
|
||||||
|
- 好きなユーザ名でログインする。ログインに関する設定がデフォルトの場合、パスワードは何を入れてもいい。
|
||||||
|
|
||||||
***:** サーバーを動作させるだけならjreのみで十分です。 開発をしたい場合JDKが必要になるかもしれません。
|
|
||||||
|
|
||||||
* [MongoDB](https://www.mongodb.com/try/download/community) (バージョン4.0以降を推奨)
|
|
||||||
|
|
||||||
* プロキシツール: [mitmproxy](https://mitmproxy.org/) (mitmdump, 推奨)、[Fiddler Classic](https://telerik-fiddler.s3.amazonaws.com/fiddler/FiddlerSetup.exe)、その他。
|
|
||||||
|
|
||||||
### 起動方法
|
|
||||||
|
|
||||||
***:** もしサーバーをアップデートしたい場合は`config.json`を削除してから再生成してください。
|
|
||||||
|
|
||||||
1. `grasscutter.jar`を入手する
|
|
||||||
- [releases](https://github.com/Grasscutters/Grasscutter/releases/latest) か [action](https://github.com/Grasscutters/Grasscutter/actions) からダウンロードするか、[自分でビルド](#ビルド)してください。
|
|
||||||
2. `grasscutter.jar` があるディレクトリに `resources` フォルダーを作成し、そこに `BinOutput, ExcelBinOutput, Readables, Scripts, Subtitle, TextMap` を移動してください *(`resources` フォルダの中身の入手方法については [wiki](https://github.com/Grasscutters/Grasscutter/wiki) を参照してください.)*
|
|
||||||
3. コマンドプロンプトに`java -jar grasscutter.jar`を入力しGrasscutterを起動してください。**このときMongoDBも実行する必要があります。**
|
|
||||||
|
|
||||||
### クライアントとの接続
|
|
||||||
|
|
||||||
½. [このコマンド](https://github.com/Grasscutters/Grasscutter/wiki/Commands#commands-for-server-admins)をサーバーコンソールから使用してアカウントを作成してください。
|
|
||||||
|
|
||||||
1. 通信内容をリダイレクトする: (どちらか一つを選択してください)
|
|
||||||
- mitmdump: `mitmdump -s proxy.py -k`
|
|
||||||
|
|
||||||
- CA証明書を信頼する:
|
|
||||||
|
|
||||||
- ***:** CA証明書は`%USERPROFILE%\.mitmproxy`に保存されています。ダブルクリックして[インストール](https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate#installing-a-trusted-root-certificate)するか...
|
|
||||||
|
|
||||||
- コマンドライン経由でインストールします
|
|
||||||
|
|
||||||
```shell
|
|
||||||
certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer
|
|
||||||
```
|
|
||||||
|
|
||||||
- Fiddler Classic: Fiddler Classicを起動し(Tools -> Options -> HTTPS)から`Decrypt https traffic`をオンにしてください。 (Tools -> Options -> Connections) に有るポート番号の設定を`8888`以外に設定してください。その後この[スクリプト](https://github.com/Grasscutters/Grasscutter/wiki/Resources#fiddler-classic-jscript)をFiddlerScriptタブにコピペしてロードします。
|
|
||||||
|
|
||||||
- [ホストファイル](https://github.com/Grasscutters/Grasscutter/wiki/Resources#hosts-file)
|
|
||||||
|
|
||||||
2. ネットワークプロキシを `127.0.0.1:(自分で設定したポート番号)` に設定してください。
|
|
||||||
- mitmproxyを使用した場合:プロキシの設定と証明書のインストールが終わった後、http://mitm.it/ でトラフィックがmitmproxyを通過しているか確認しましょう。
|
|
||||||
|
|
||||||
**`start.cmd`でmitmdumpとサーバーをまとめて起動することが出来ます。ただ、事前に`start_config.cmd`でJAVAのパスを指定している必要があります。**
|
|
||||||
|
|
||||||
### ビルド
|
### ビルド
|
||||||
|
|
||||||
GrasscutterはGradleを使用して依存関係とビルドを処理しています。
|
Grasscutterは依存関係とビルドの処理にGradleを使用しています。
|
||||||
|
|
||||||
**要件:**
|
**必要要件:**
|
||||||
|
|
||||||
- [Java SE Development Kits - 17以降](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
- [Java SE Development Kit 17以降](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
- [NodeJS](https://nodejs.org/en/download) (任意、ハンドブックの生成に必要)
|
||||||
|
|
||||||
##### Windows
|
##### Clone
|
||||||
|
```shell
|
||||||
|
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
||||||
|
cd Grasscutter
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Compile
|
||||||
|
|
||||||
|
**Note:** 環境によってはハンドブックの生成が失敗する場合があります。ハンドブックの生成をさせない場合は `gradlew jar` コマンドに `-PskipHandbook=1` を付け加えてください。
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/Grasscutters/Grasscutter.git
|
git clone https://github.com/Grasscutters/Grasscutter.git
|
||||||
@ -86,7 +69,7 @@ cd Grasscutter
|
|||||||
.\gradlew jar # コンパイル
|
.\gradlew jar # コンパイル
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Linux
|
Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Grasscutters/Grasscutter.git
|
git clone https://github.com/Grasscutters/Grasscutter.git
|
||||||
@ -95,13 +78,23 @@ chmod +x gradlew
|
|||||||
./gradlew jar # コンパイル
|
./gradlew jar # コンパイル
|
||||||
```
|
```
|
||||||
|
|
||||||
生成されたjarファイルはプロジェクトフォルダのルートに有ります。
|
##### 手動によるハンドブックの生成
|
||||||
|
|
||||||
### コマンドリストは[wiki](https://github.com/Grasscutters/Grasscutter/wiki/Commands)へ移動しました。
|
Gradleを使用する場合:
|
||||||
|
```shell
|
||||||
|
./gradlew generateHandbook
|
||||||
|
```
|
||||||
|
|
||||||
# トラブルシューティング
|
NPMを使用する場合:
|
||||||
|
```shell
|
||||||
|
cd src/handbook
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
* コンパイルが失敗した場合JDKがインストールされているか確認してください。(JDKのバージョンが17以降であることと、環境変数でJDKのパスが設定されている必要があります)
|
|
||||||
* クライアントが接続できない・ログインできない・エラーコード4206・またその他場合、ほとんどは、プロキシデーモンの設定が問題です。Fiddlerを使っている場合はデフォルトポートを8888以外の別のポートに変更してみてください。
|
生成されたjarファイルはプロジェクトのルートフォルダにあります。
|
||||||
Fiddlerを使用している場合はポートが8888以外に設定されていることを確認してください。
|
|
||||||
* 起動シーケンス(順番): MongoDB > Grasscutter > プロキシツール (mitmdumpかfiddler、その他) > ゲーム
|
### トラブルシューティング
|
||||||
|
|
||||||
|
よく散見されるトラブルとそれに対する解決策のまとめリストや、質問し誰かの助けを得たい場合は、Grasscutterの[Discordサーバー](https://discord.gg/T5vZU6UyeG)に参加し、サポートチャンネルを参照してください。
|
||||||
|
@ -22,52 +22,25 @@
|
|||||||
|
|
||||||
**각주 :** 도움이 필요할 경우 [Discord](https://discord.gg/T5vZU6UyeG)에 가입하세요.
|
**각주 :** 도움이 필요할 경우 [Discord](https://discord.gg/T5vZU6UyeG)에 가입하세요.
|
||||||
|
|
||||||
### 설치에 필요한 것들
|
### 빠른 설치 (자동)
|
||||||
|
|
||||||
* Java SE - 17 ([링크](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html))
|
- [Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) 설치
|
||||||
|
- [MongoDB Community Server](https://www.mongodb.com/try/download/community) 설치
|
||||||
|
- 게임 버전 REL4.0.x 다운로드 (만약 4.0.x 클라이언트를 가지고 있지 않다면, 여기서 찾을 수 있습니다.):
|
||||||
|
[4.0.x 클라이언트 - GitHub](https://github.com/JRSKelvin/GenshinRepository/blob/main/Version%204.0.0.md)
|
||||||
|
[4.0.x 클라이언트 - 구글 드라이브브](https://www.123pan.com/s/HoqUVv-U7SBA.html)
|
||||||
|
|
||||||
**각주 :** **실행**만을 원한다면, **jre**만 있어도 괜찮습니다.
|
- [최신 Cultivation](https://github.com/Grasscutters/Cultivation/releases/latest) 다운로드하세요. `.msi` 설치파일을 사용하면 됩니다.
|
||||||
|
- (관리자 권한으로) Cultivation을 실행한 후, 우측 상단에 위치한 다운로드 버튼을 클릭하세요.
|
||||||
|
- `올인원 다운로드`를 클릭하세요.
|
||||||
|
- 우측 상단에 위치한 톱니바퀴 버튼을 누르세요.
|
||||||
|
- 게임 설치 경로를 게임이 위치한 경로로 설정하세요.
|
||||||
|
- 사용자 지정 Java 경로 설정을 `C:\Program Files\Java\jdk-17\bin\java.exe`로 설정하세요.
|
||||||
|
- 다른 모든 설정은 기본값으로 두세요.
|
||||||
|
|
||||||
* [MongoDB](https://www.mongodb.com/try/download/community) (4.0 이상의 버전 추천)
|
- 게임 시작 버튼 옆에 위치한 작은 버튼을 누르세요.
|
||||||
|
- 게임 시작 버튼을 누르세요.
|
||||||
* 프록시 데몬 : mitmproxy (mitmdump 추천), Fiddler Classic 등.
|
- 원하는 사용자 이름으로 로그인하세요. 비밀번호는 무엇이든 가능합니다.
|
||||||
|
|
||||||
### 실행
|
|
||||||
|
|
||||||
**각주 :** 구버전에서 업데이트 했을 경우, `config.json` 파일을 재생성하기 위해 파일을 삭제하세요.
|
|
||||||
|
|
||||||
1. `grasscutter.jar` 얻기
|
|
||||||
- [Actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 탭에서 다운로드
|
|
||||||
- [직접 빌드하기](#빌드하기)
|
|
||||||
2. grasscutter.jar 파일이 위치한 폴더에 `resources` 폴더를 생성하고, `BinOutput` 과 `ExcelBinOutput` 폴더를 생성한 폴더 내로 옮기세요. *(이 파일들을 얻는 더 자세한 방법에 대해서는 [위키](https://github.com/Grasscutters/Grasscutter/wiki)를 참조하세요.)*
|
|
||||||
3. Grasscutter를 `java -jar grasscutter.jar` 명령어로 실행합니다. **MongoDB 서비스가 정상적으로 실행되고 있는지 확인하세요.**
|
|
||||||
|
|
||||||
### 클라이언트와의 연결
|
|
||||||
|
|
||||||
½. [서버 콘솔 명령어](https://github.com/Grasscutters/Grasscutter/wiki/Commands#targeting)를 이용해서 계정을 생성합니다.
|
|
||||||
|
|
||||||
1. 리다이렉트 트래픽 : (1가지 선택)
|
|
||||||
- mitmdump: `mitmdump -s proxy.py -k`
|
|
||||||
|
|
||||||
신뢰하는 인증 기관 인증서 (CA Cert) :
|
|
||||||
|
|
||||||
**각주 :** CA 인증서는 보통 `%USERPROFILE%\ .mitmproxy` 경로에 저장되며, `http://mitm.it`에서 다운로드 받을 수도 있습니다.
|
|
||||||
|
|
||||||
더블 클릭하여 [설치](https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate#installing-a-trusted-root-certificate) 또는 ...
|
|
||||||
|
|
||||||
- 명령어를 통해서
|
|
||||||
|
|
||||||
```shell
|
|
||||||
certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer
|
|
||||||
```
|
|
||||||
|
|
||||||
- Fiddler Classic : Fiddler Classic을 실행한 후, Setting에서 `Decrypt https traffic` 옵션을 켜고, Tools -> Options -> Connections에 있는 기본 포트를 `8888`을 제외한 다른 포트로 지정합니다. 그리고 [이 스크립트](https://github.lunatic.moe/fiddlerscript)를 불러옵니다.
|
|
||||||
|
|
||||||
- [호스트 파일](https://github.com/Grasscutters/Grasscutter/wiki/Running#traffic-route-map)
|
|
||||||
|
|
||||||
2. 네트워크 프록시를 `127.0.0.1:8080` 로 설정하거나 지정한 프록시 포트로 설정합니다.
|
|
||||||
|
|
||||||
**또한 `start.cmd`를 실행함으로써, 서버와 프록시 데몬을 자동으로 실행되게 할 수 있습니다. 이를 이용하기 위해서는 JAVA_HOME 환경 변수를 등록해야 합니다.**
|
|
||||||
|
|
||||||
### 빌드하기
|
### 빌드하기
|
||||||
|
|
||||||
@ -77,39 +50,50 @@ Grasscutter는 종속성 및 컴파일 처리를 위해 Gradle을 이용합니
|
|||||||
|
|
||||||
- [Java SE 개발 키트 - 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
- [Java SE 개발 키트 - 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
- [NodeJS](https://nodejs.org/en/download) (선택, 핸드북을 빌드하기 위해 필요함.)
|
||||||
|
|
||||||
##### 윈도우 (온라인)
|
##### 클론
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/Grasscutters/Grasscutter.git
|
git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git
|
||||||
cd Grasscutter
|
cd Grasscutter
|
||||||
.\gradlew.bat # 개발 환경 설정
|
|
||||||
.\gradlew jar # 컴파일
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 윈도우 (로컬)
|
##### 컴파일
|
||||||
|
|
||||||
|
**각주**: 핸드북 생성은 일부 시스템에서 실패할 수도 있습니다. 핸드북 생성을 비활성화하려면, `gradlew jar`명령에 `-PskipHandbook=1`명령줄 스위치를 추가하세요.
|
||||||
|
|
||||||
|
|
||||||
|
윈도우:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd <로컬 주소>/Grasscutter
|
.\gradlew.bat # 환경 준비
|
||||||
.\gradlew.bat # 개발 환경 설정
|
.\gradlew jar
|
||||||
.\gradlew jar # 컴파일
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 리눅스
|
리눅스 (GNU):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Grasscutters/Grasscutter.git
|
|
||||||
cd Grasscutter
|
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew jar # 컴파일
|
./gradlew jar
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 핸드북 컴파일 (수동동)
|
||||||
|
|
||||||
|
Gradle 사용:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./gradlew generateHandbook
|
||||||
|
```
|
||||||
|
|
||||||
|
NPM 사용:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd src/handbook
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
프로젝트 폴더의 최상단에서 jar 파일을 찾을 수 있습니다.
|
프로젝트 폴더의 최상단에서 jar 파일을 찾을 수 있습니다.
|
||||||
|
|
||||||
### 명령어들은 [위키](https://github.com/Grasscutters/Grasscutter/wiki/Commands)에서 확인할 수 있습니다.
|
### 문제 해결
|
||||||
|
흔한 문제들의 해결방법과 도움을 요청하려면, [우리의 디스코드 서버](https://discord.gg/T5vZU6UyeG)에 참가하고 support 채널에 가보세요.
|
||||||
# 빠른 문제 해결
|
|
||||||
|
|
||||||
* 만약 컴파일링이 정상적으로 완료되지 않을 경우, JDK 설치를 확인하세요. (JDK 버전 17 및 JDK의 bin 경로 변수 등록을 확인)
|
|
||||||
* 클라이언트가 연결되지 않거나, 로그인이 안 되거나, 4206 오류가 뜨는 등의 경우 - 대부분 프록시 데몬의 설치에 문제가 있을 것입니다. Fiddler를 사용하고 있다면, 8888을 제외한 다른 포트에서 구동되고 있는지 확인하세요.
|
|
||||||
* 구동 순서 : MongoDB > Grasscutter > 프록시 데몬 (mitmdump, fiddler 등) > 게임
|
|
@ -26,10 +26,12 @@
|
|||||||
|
|
||||||
- 获取Java 17:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
- 获取Java 17:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||||
- 获取[MongoDB社区版](https://www.mongodb.com/try/download/community)
|
- 获取[MongoDB社区版](https://www.mongodb.com/try/download/community)
|
||||||
- 获取游戏4.0正式版 (如果你没有4.0的客户端,可以在这里找到):https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/4.0.0.md)
|
- 获取游戏4.0正式版 (如果你没有4.0的客户端,可以在这里找到):
|
||||||
|
[123pan share](https://www.123pan.com/s/HoqUVv-U7SBA.html)
|
||||||
|
[github](https://github.com/JRSKelvin/GenshinRepository/blob/main/Version%204.0.0.md)
|
||||||
|
|
||||||
- 下载[最新的Cultivation版本](https://github.com/Grasscutters/Cultivation/releases/latest)(使用以“.msi”为后缀的安装包)。
|
- 下载[最新的Cultivation版本](https://github.com/Grasscutters/Cultivation/releases/latest)(使用以“.msi”为后缀的安装包)。
|
||||||
- 以管理员身份打开Culivation,按右上角的下载按钮。
|
- 以管理员身份打开Cultivation,按右上角的下载按钮。
|
||||||
- 点击“下载 Grasscutter 一体化”
|
- 点击“下载 Grasscutter 一体化”
|
||||||
- 点击右上角的齿轮
|
- 点击右上角的齿轮
|
||||||
- 将游戏安装路径设置为你游戏所在的位置。
|
- 将游戏安装路径设置为你游戏所在的位置。
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
- 下載遊戲版本 REL3.7(如果你沒有的話,可以在[這裡](https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/3.7.0.md)找到 3.7 客戶端)
|
- 下載遊戲版本 REL3.7(如果你沒有的話,可以在[這裡](https://github.com/MAnggiarMustofa/GI-Download-Library/blob/main/GenshinImpact/Client/3.7.0.md)找到 3.7 客戶端)
|
||||||
|
|
||||||
- 下載 [最新的 Cultivation 版本](https://github.com/Grasscutters/Cultivation/releases/latest)。使用 `.msi` 安裝程式。
|
- 下載 [最新的 Cultivation 版本](https://github.com/Grasscutters/Cultivation/releases/latest)。使用 `.msi` 安裝程式。
|
||||||
- 以管理員身分打開 Culivation,按右上角的下載按鈕。
|
- 以管理員身分打開 Cultivation,按右上角的下載按鈕。
|
||||||
- 點擊 `Download All-in-One`
|
- 點擊 `Download All-in-One`
|
||||||
- 點擊右上角的齒輪
|
- 點擊右上角的齒輪
|
||||||
- 將遊戲安裝路徑設置為你的遊戲所在的位置。
|
- 將遊戲安裝路徑設置為你的遊戲所在的位置。
|
||||||
|
3
entrypoint.sh
Normal file
3
entrypoint.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#/bin/sh
|
||||||
|
|
||||||
|
java -jar /app/grasscutter.jar
|
@ -17,9 +17,11 @@ public final class StopCommand implements CommandHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||||
CommandHandler.sendMessage(null, translate("commands.stop.success"));
|
CommandHandler.sendMessage(null, translate("commands.stop.success"));
|
||||||
|
if (Grasscutter.getGameServer() != null) {
|
||||||
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
|
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
|
||||||
CommandHandler.sendMessage(p, translate(p, "commands.stop.success"));
|
CommandHandler.sendMessage(p, translate(p, "commands.stop.success"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System.exit(1000);
|
System.exit(1000);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ public class ConfigContainer {
|
|||||||
public boolean autoCreate = false;
|
public boolean autoCreate = false;
|
||||||
public boolean EXPERIMENTAL_RealPassword = false;
|
public boolean EXPERIMENTAL_RealPassword = false;
|
||||||
public String[] defaultPermissions = {};
|
public String[] defaultPermissions = {};
|
||||||
|
public String playerEmail = "grasscutter.io";
|
||||||
public int maxPlayer = -1;
|
public int maxPlayer = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +302,10 @@ public final class GameData {
|
|||||||
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
|
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final Int2ObjectMap<HomeWorldModuleData> homeWorldModuleDataMap =
|
||||||
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static final Int2ObjectMap<HomeWorldNPCData> homeWorldNPCDataMap =
|
private static final Int2ObjectMap<HomeWorldNPCData> homeWorldNPCDataMap =
|
||||||
new Int2ObjectOpenHashMap<>();
|
new Int2ObjectOpenHashMap<>();
|
||||||
|
@ -42,6 +42,7 @@ public class AbilityModifier implements Serializable {
|
|||||||
public String stacking;
|
public String stacking;
|
||||||
|
|
||||||
public AbilityMixinData[] modifierMixins;
|
public AbilityMixinData[] modifierMixins;
|
||||||
|
public AbilityModifierProperty properties;
|
||||||
|
|
||||||
public ElementType elementType;
|
public ElementType elementType;
|
||||||
public DynamicFloat elementDurability = DynamicFloat.ZERO;
|
public DynamicFloat elementDurability = DynamicFloat.ZERO;
|
||||||
@ -327,6 +328,9 @@ public class AbilityModifier implements Serializable {
|
|||||||
public String srcKey, dstKey;
|
public String srcKey, dstKey;
|
||||||
|
|
||||||
public int skillID;
|
public int skillID;
|
||||||
|
public int resistanceListID;
|
||||||
|
public int monsterID;
|
||||||
|
public int summonTag;
|
||||||
|
|
||||||
public AbilityModifierAction[] actions;
|
public AbilityModifierAction[] actions;
|
||||||
public AbilityModifierAction[] successActions;
|
public AbilityModifierAction[] successActions;
|
||||||
@ -369,6 +373,11 @@ public class AbilityModifier implements Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AbilityModifierProperty implements Serializable {
|
||||||
|
public float Actor_HpThresholdRatio;
|
||||||
|
// Add more properties here when GC needs them.
|
||||||
|
}
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
LockHP,
|
LockHP,
|
||||||
Invincible,
|
Invincible,
|
||||||
|
@ -8,4 +8,5 @@ import lombok.experimental.FieldDefaults;
|
|||||||
public class ConfigCombat {
|
public class ConfigCombat {
|
||||||
// There are more values that can be added that might be useful in the json
|
// There are more values that can be added that might be useful in the json
|
||||||
ConfigCombatProperty property;
|
ConfigCombatProperty property;
|
||||||
|
ConfigCombatSummon summon;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package emu.grasscutter.data.binout.config.fields;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ConfigCombatSummon {
|
||||||
|
List<SummonTag> summonTags;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public final class SummonTag {
|
||||||
|
int summonTag;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package emu.grasscutter.data.excels;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameResource;
|
||||||
|
import emu.grasscutter.data.ResourceType;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ResourceType(name = "HomeworldModuleExcelConfigData.json")
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
|
public class HomeWorldModuleData extends GameResource {
|
||||||
|
int Id;
|
||||||
|
boolean isFree;
|
||||||
|
int worldSceneId;
|
||||||
|
int defaultRoomSceneId;
|
||||||
|
}
|
@ -13,6 +13,7 @@ public class TowerLevelData extends GameResource {
|
|||||||
private int levelGroupId;
|
private int levelGroupId;
|
||||||
private int dungeonId;
|
private int dungeonId;
|
||||||
private List<TowerLevelCond> conds;
|
private List<TowerLevelCond> conds;
|
||||||
|
private int monsterLevel;
|
||||||
|
|
||||||
public static class TowerLevelCond {
|
public static class TowerLevelCond {
|
||||||
private TowerCondType towerCondType;
|
private TowerCondType towerCondType;
|
||||||
|
@ -109,7 +109,7 @@ public class Account {
|
|||||||
return email;
|
return email;
|
||||||
} else {
|
} else {
|
||||||
// As of game version 3.5+, only the email is displayed to a user.
|
// As of game version 3.5+, only the email is displayed to a user.
|
||||||
return this.getUsername() + "@grasscutter.io";
|
return this.getUsername() + "@" + ACCOUNT.playerEmail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ public class Account {
|
|||||||
this.addPermission("*");
|
this.addPermission("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set account default language as server default language
|
// Set account default language to server default language
|
||||||
if (!document.containsKey("locale")) {
|
if (!document.containsKey("locale")) {
|
||||||
this.locale = LANGUAGE;
|
this.locale = LANGUAGE;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package emu.grasscutter.game.ability;
|
|||||||
|
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.binout.AbilityData;
|
import emu.grasscutter.data.binout.AbilityData;
|
||||||
|
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
||||||
import emu.grasscutter.game.entity.GameEntity;
|
import emu.grasscutter.game.entity.GameEntity;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.proto.AbilityStringOuterClass.AbilityString;
|
import emu.grasscutter.net.proto.AbilityStringOuterClass.AbilityString;
|
||||||
@ -24,6 +25,7 @@ public class Ability {
|
|||||||
private static Map<String, Object2FloatMap<String>> abilitySpecialsModified = new HashMap<>();
|
private static Map<String, Object2FloatMap<String>> abilitySpecialsModified = new HashMap<>();
|
||||||
|
|
||||||
@Getter private int hash;
|
@Getter private int hash;
|
||||||
|
@Getter private Set<Integer> avatarSkillStartIds;
|
||||||
|
|
||||||
public Ability(AbilityData data, GameEntity owner, Player playerOwner) {
|
public Ability(AbilityData data, GameEntity owner, Player playerOwner) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -44,6 +46,49 @@ public class Ability {
|
|||||||
hash = Utils.abilityHash(data.abilityName);
|
hash = Utils.abilityHash(data.abilityName);
|
||||||
|
|
||||||
data.initialize();
|
data.initialize();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Collect skill IDs referenced by AvatarSkillStart modifier actions
|
||||||
|
// in onAbilityStart and in every modifier's onAdded action set.
|
||||||
|
// These skill IDs will be used by AbilityManager to determine whether
|
||||||
|
// an elemental burst has fired correctly.
|
||||||
|
//
|
||||||
|
avatarSkillStartIds = new HashSet<>();
|
||||||
|
if (data.onAbilityStart != null) {
|
||||||
|
avatarSkillStartIds.addAll(
|
||||||
|
Arrays.stream(data.onAbilityStart)
|
||||||
|
.filter(action -> action.type == AbilityModifierAction.Type.AvatarSkillStart)
|
||||||
|
.map(action -> action.skillID)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
avatarSkillStartIds.addAll(
|
||||||
|
data.modifiers.values().stream()
|
||||||
|
.map(
|
||||||
|
m ->
|
||||||
|
(List<AbilityModifierAction>)
|
||||||
|
(m.onAdded == null ? Collections.emptyList() : Arrays.asList(m.onAdded)))
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.filter(action -> action.type == AbilityModifierAction.Type.AvatarSkillStart)
|
||||||
|
.map(action -> action.skillID)
|
||||||
|
.toList());
|
||||||
|
|
||||||
|
if (data.onAdded != null) {
|
||||||
|
processOnAddedAbilityModifiers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processOnAddedAbilityModifiers() {
|
||||||
|
for (AbilityModifierAction modifierAction : data.onAdded) {
|
||||||
|
if (modifierAction.type == null) continue;
|
||||||
|
|
||||||
|
if (modifierAction.type == AbilityModifierAction.Type.ApplyModifier) {
|
||||||
|
if (modifierAction.modifierName == null) continue;
|
||||||
|
else if (!data.modifiers.containsKey(modifierAction.modifierName)) continue;
|
||||||
|
|
||||||
|
var modifierData = data.modifiers.get(modifierAction.modifierName);
|
||||||
|
owner.onAddAbilityModifier(modifierData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAbilityName(AbilityString abString) {
|
public static String getAbilityName(AbilityString abString) {
|
||||||
|
@ -21,7 +21,7 @@ import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalar
|
|||||||
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
|
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
|
||||||
import emu.grasscutter.server.event.player.PlayerUseSkillEvent;
|
import emu.grasscutter.server.event.player.PlayerUseSkillEvent;
|
||||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@ -48,9 +48,64 @@ public final class AbilityManager extends BasePlayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Getter private boolean abilityInvulnerable = false;
|
@Getter private boolean abilityInvulnerable = false;
|
||||||
|
private int burstCasterId;
|
||||||
|
private int burstSkillId;
|
||||||
|
|
||||||
public AbilityManager(Player player) {
|
public AbilityManager(Player player) {
|
||||||
super(player);
|
super(player);
|
||||||
|
removePendingEnergyClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePendingEnergyClear() {
|
||||||
|
this.burstCasterId = 0;
|
||||||
|
this.burstSkillId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPossibleElementalBurst(Ability ability, AbilityModifier modifier, int entityId) {
|
||||||
|
//
|
||||||
|
// Possibly clear avatar energy spent on elemental burst
|
||||||
|
// and set invulnerability.
|
||||||
|
//
|
||||||
|
// Problem: Burst can misfire occasionally, like hitting Q when
|
||||||
|
// dashing, doing E, or switching avatars. The client would
|
||||||
|
// still send EvtDoSkillSuccNotify, but the burst may not
|
||||||
|
// actually happen. We don't know when to clear avatar energy.
|
||||||
|
//
|
||||||
|
// When burst does happen, a number of AbilityInvokeEntry will
|
||||||
|
// come in. Use the Ability it references and search for any
|
||||||
|
// modifier with type=AvatarSkillStart, skillID=burst skill ID.
|
||||||
|
//
|
||||||
|
// If that is missing, search for modifier action that sets
|
||||||
|
// invulnerability as a fallback.
|
||||||
|
//
|
||||||
|
if (this.burstCasterId == 0) return;
|
||||||
|
|
||||||
|
boolean skillInvincibility = modifier.state == AbilityModifier.State.Invincible;
|
||||||
|
if (modifier.onAdded != null) {
|
||||||
|
skillInvincibility |=
|
||||||
|
Arrays.stream(modifier.onAdded)
|
||||||
|
.filter(
|
||||||
|
action ->
|
||||||
|
action.type == AbilityModifierAction.Type.AttachAbilityStateResistance
|
||||||
|
&& action.resistanceListID == 11002)
|
||||||
|
.toList()
|
||||||
|
.size()
|
||||||
|
> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.burstCasterId == entityId
|
||||||
|
&& (ability.getAvatarSkillStartIds().contains(this.burstSkillId) || skillInvincibility)) {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.trace(
|
||||||
|
"Caster ID's {} burst successful, clearing energy and setting invulnerability",
|
||||||
|
entityId);
|
||||||
|
this.abilityInvulnerable = true;
|
||||||
|
this.player
|
||||||
|
.getEnergyManager()
|
||||||
|
.handleEvtDoSkillSuccNotify(
|
||||||
|
this.player.getSession(), this.burstSkillId, this.burstCasterId);
|
||||||
|
this.removePendingEnergyClear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerHandlers() {
|
public static void registerHandlers() {
|
||||||
@ -280,8 +335,9 @@ public final class AbilityManager extends BasePlayerManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the player as invulnerable.
|
// Track this elemental burst to possibly clear avatar energy later.
|
||||||
this.abilityInvulnerable = true;
|
this.burstSkillId = skillId;
|
||||||
|
this.burstCasterId = casterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -454,6 +510,8 @@ public final class AbilityManager extends BasePlayerManager {
|
|||||||
modifierData);
|
modifierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPossibleElementalBurst(instancedAbility, modifierData, invoke.getEntityId());
|
||||||
|
|
||||||
AbilityModifierController modifier =
|
AbilityModifierController modifier =
|
||||||
new AbilityModifierController(instancedAbility, instancedAbilityData, modifierData);
|
new AbilityModifierController(instancedAbility, instancedAbilityData, modifierData);
|
||||||
|
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package emu.grasscutter.game.ability.actions;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
||||||
|
import emu.grasscutter.game.ability.Ability;
|
||||||
|
import emu.grasscutter.game.entity.*;
|
||||||
|
import emu.grasscutter.game.world.*;
|
||||||
|
import emu.grasscutter.net.proto.EPKDEHOJFLIOuterClass.EPKDEHOJFLI;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketMonsterSummonTagNotify;
|
||||||
|
import emu.grasscutter.utils.*;
|
||||||
|
|
||||||
|
@AbilityAction(AbilityModifierAction.Type.Summon)
|
||||||
|
public class ActionSummon extends AbilityActionHandler {
|
||||||
|
@Override
|
||||||
|
public synchronized boolean execute(
|
||||||
|
Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) {
|
||||||
|
EPKDEHOJFLI summonPosRot = null;
|
||||||
|
try {
|
||||||
|
// In game version 4.0, summoned entity's
|
||||||
|
// position and rotation are packed in EPKDEHOJFLI.
|
||||||
|
// This is packet AbilityActionSummon and has two fields:
|
||||||
|
// 4: Vector pos
|
||||||
|
// 13: Vector rot
|
||||||
|
summonPosRot = EPKDEHOJFLI.parseFrom(abilityData);
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.error("Failed to parse abilityData: {}", Utils.bytesToHex(abilityData.toByteArray()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = new Position(summonPosRot.getPos());
|
||||||
|
var rot = new Position(summonPosRot.getRot());
|
||||||
|
var monsterId = action.monsterID;
|
||||||
|
|
||||||
|
var scene = target.getScene();
|
||||||
|
|
||||||
|
var monsterData = GameData.getMonsterDataMap().get(monsterId);
|
||||||
|
if (monsterData == null) {
|
||||||
|
Grasscutter.getLogger().error("Failed to find monster by ID {}", monsterId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof EntityMonster ownerEntity) {
|
||||||
|
var level = scene.getLevelForMonster(0, ownerEntity.getLevel());
|
||||||
|
var entity = new EntityMonster(scene, monsterData, pos, rot, level);
|
||||||
|
ownerEntity.getSummonTagMap().put(action.summonTag, entity);
|
||||||
|
entity.setSummonedTag(action.summonTag);
|
||||||
|
entity.setOwnerEntityId(target.getId());
|
||||||
|
scene.addEntity(entity);
|
||||||
|
scene.getPlayers().get(0).sendPacket(new PacketMonsterSummonTagNotify(ownerEntity));
|
||||||
|
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.trace(
|
||||||
|
"Spawned entityId {} monsterId {} pos {} rot {}, target { {} }, action { {} }",
|
||||||
|
entity.getId(),
|
||||||
|
monsterId,
|
||||||
|
pos,
|
||||||
|
rot,
|
||||||
|
target,
|
||||||
|
action);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,10 @@ public final class DungeonManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFinishedSuccessfully()) {
|
if (isFinishedSuccessfully()) {
|
||||||
|
// Set ended now because calling EVENT_DUNGEON_SETTLE
|
||||||
|
// during finishDungeon() may cause reentrance into
|
||||||
|
// this function, leading to double settles.
|
||||||
|
ended = true;
|
||||||
finishDungeon();
|
finishDungeon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,9 +82,14 @@ public final class DungeonManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getLevelForMonster(int id) {
|
public int getLevelForMonster(int id) {
|
||||||
|
if (isTowerDungeon()) {
|
||||||
|
// Tower dungeons have their own level setting in TowerLevelData
|
||||||
|
return scene.getPlayers().get(0).getTowerManager().getCurrentMonsterLevel();
|
||||||
|
} else {
|
||||||
// TODO should use levelConfigMap? and how?
|
// TODO should use levelConfigMap? and how?
|
||||||
return dungeonData.getShowLevel();
|
return dungeonData.getShowLevel();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean activateRespawnPoint(int pointId) {
|
public boolean activateRespawnPoint(int pointId) {
|
||||||
val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId);
|
val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package emu.grasscutter.game.dungeons;
|
package emu.grasscutter.game.dungeons;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
|
||||||
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
|
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
|
||||||
import emu.grasscutter.game.dungeons.dungeon_results.TowerResult;
|
import emu.grasscutter.game.dungeons.dungeon_results.TowerResult;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
@ -25,16 +26,22 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
|
|||||||
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
||||||
var stars = towerManager.getCurLevelStars();
|
var stars = towerManager.getCurLevelStars();
|
||||||
|
|
||||||
|
if (endReason == DungeonEndReason.COMPLETED) {
|
||||||
|
// Update star record only when challenge completes successfully.
|
||||||
towerManager.notifyCurLevelRecordChangeWhenDone(stars);
|
towerManager.notifyCurLevelRecordChangeWhenDone(stars);
|
||||||
scene.broadcastPacket(
|
scene.broadcastPacket(
|
||||||
new PacketTowerFloorRecordChangeNotify(
|
new PacketTowerFloorRecordChangeNotify(
|
||||||
towerManager.getCurrentFloorId(), stars, towerManager.canEnterScheduleFloor()));
|
towerManager.getCurrentFloorId(), stars, towerManager.canEnterScheduleFloor()));
|
||||||
|
}
|
||||||
|
|
||||||
var challenge = scene.getChallenge();
|
var challenge = scene.getChallenge();
|
||||||
|
var finishedTime = challenge == null ? challenge.getFinishedTime() : 0;
|
||||||
var dungeonStats =
|
var dungeonStats =
|
||||||
new DungeonEndStats(
|
new DungeonEndStats(scene.getKilledMonsterCount(), finishedTime, 0, endReason);
|
||||||
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
|
var result =
|
||||||
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge, stars);
|
endReason == DungeonEndReason.COMPLETED
|
||||||
|
? new TowerResult(dungeonData, dungeonStats, towerManager, challenge, stars)
|
||||||
|
: new BaseDungeonResult(dungeonData, dungeonStats);
|
||||||
|
|
||||||
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
|
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import emu.grasscutter.Grasscutter;
|
|||||||
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
|
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
|
||||||
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
|
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.*;
|
||||||
|
import emu.grasscutter.game.props.FightProperty;
|
||||||
import emu.grasscutter.game.props.WatcherTriggerType;
|
import emu.grasscutter.game.props.WatcherTriggerType;
|
||||||
import emu.grasscutter.game.world.Scene;
|
import emu.grasscutter.game.world.Scene;
|
||||||
import emu.grasscutter.scripts.constants.EventType;
|
import emu.grasscutter.scripts.constants.EventType;
|
||||||
@ -22,12 +23,13 @@ public class WorldChallenge {
|
|||||||
private final int challengeIndex;
|
private final int challengeIndex;
|
||||||
private final List<Integer> paramList;
|
private final List<Integer> paramList;
|
||||||
private int timeLimit;
|
private int timeLimit;
|
||||||
|
private GameEntity guardEntity;
|
||||||
private final List<ChallengeTrigger> challengeTriggers;
|
private final List<ChallengeTrigger> challengeTriggers;
|
||||||
private final int goal;
|
private final int goal;
|
||||||
private final AtomicInteger score;
|
private final AtomicInteger score;
|
||||||
private boolean progress;
|
private boolean progress;
|
||||||
private boolean success;
|
private boolean success;
|
||||||
private long startedAt;
|
private int startedAt;
|
||||||
private int finishedTime;
|
private int finishedTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +60,7 @@ public class WorldChallenge {
|
|||||||
this.challengeTriggers = challengeTriggers;
|
this.challengeTriggers = challengeTriggers;
|
||||||
this.goal = goal;
|
this.goal = goal;
|
||||||
this.score = new AtomicInteger(0);
|
this.score = new AtomicInteger(0);
|
||||||
|
this.guardEntity = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean inProgress() {
|
public boolean inProgress() {
|
||||||
@ -143,6 +146,10 @@ public class WorldChallenge {
|
|||||||
this.progress = false;
|
this.progress = false;
|
||||||
this.success = success;
|
this.success = success;
|
||||||
this.finishedTime = (int) ((this.scene.getSceneTimeSeconds() - this.startedAt));
|
this.finishedTime = (int) ((this.scene.getSceneTimeSeconds() - this.startedAt));
|
||||||
|
|
||||||
|
// Despawn all leftover mobs in this challenge's SceneGroup
|
||||||
|
getScene().getScriptManager().removeMonstersInGroup(group);
|
||||||
|
|
||||||
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
|
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +157,20 @@ public class WorldChallenge {
|
|||||||
return score.incrementAndGet();
|
return score.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGuardEntityHpPercent() {
|
||||||
|
if (guardEntity == null) {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.warn(
|
||||||
|
"getGuardEntityHpPercent: Could not find guardEntity for this challenge = {}", this);
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
var curHp = guardEntity.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
|
||||||
|
var maxHp = guardEntity.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
|
||||||
|
int percent = (int) (curHp * 100 / maxHp);
|
||||||
|
return percent;
|
||||||
|
}
|
||||||
|
|
||||||
public void onMonsterDeath(EntityMonster monster) {
|
public void onMonsterDeath(EntityMonster monster) {
|
||||||
if (!inProgress()) {
|
if (!inProgress()) {
|
||||||
return;
|
return;
|
||||||
|
@ -33,7 +33,7 @@ public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHand
|
|||||||
realGroup,
|
realGroup,
|
||||||
challengeId, // Id
|
challengeId, // Id
|
||||||
challengeIndex, // Index
|
challengeIndex, // Index
|
||||||
List.of(monstersToKill, 0),
|
List.of(monstersToKill, gadgetCFGId),
|
||||||
0, // Limit
|
0, // Limit
|
||||||
monstersToKill, // Goal
|
monstersToKill, // Goal
|
||||||
List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId)));
|
List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId)));
|
||||||
|
@ -36,6 +36,6 @@ public class KillMonsterCountInTimeIncChallengeFactoryHandler implements Challen
|
|||||||
List.of(
|
List.of(
|
||||||
new KillMonsterCountTrigger(),
|
new KillMonsterCountTrigger(),
|
||||||
new InTimeTrigger(),
|
new InTimeTrigger(),
|
||||||
new KillMonsterTimeIncTrigger(timeInc)));
|
new KillMonsterTimeIncTrigger(timeLimit, timeInc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||||
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||||
import emu.grasscutter.game.dungeons.challenge.trigger.*;
|
import emu.grasscutter.game.dungeons.challenge.trigger.*;
|
||||||
import emu.grasscutter.game.world.Scene;
|
import emu.grasscutter.game.world.Scene;
|
||||||
import emu.grasscutter.scripts.data.SceneGroup;
|
import emu.grasscutter.scripts.data.SceneGroup;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
|
||||||
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
|
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||||
@ -28,6 +29,16 @@ public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryH
|
|||||||
Scene scene,
|
Scene scene,
|
||||||
SceneGroup group) {
|
SceneGroup group) {
|
||||||
val realGroup = scene.getScriptManager().getGroupById(groupId);
|
val realGroup = scene.getScriptManager().getGroupById(groupId);
|
||||||
|
val challengeTriggers = new ArrayList<ChallengeTrigger>();
|
||||||
|
challengeTriggers.addAll(List.of(new KillMonsterCountTrigger(), new InTimeTrigger()));
|
||||||
|
|
||||||
|
val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeId);
|
||||||
|
val challengeType = challengeData.getChallengeType();
|
||||||
|
if (challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST) {
|
||||||
|
challengeTriggers.add(
|
||||||
|
new KillMonsterTimeIncTrigger(timeLimit, 0 /* refresh to original limit on kill */));
|
||||||
|
}
|
||||||
|
|
||||||
return new WorldChallenge(
|
return new WorldChallenge(
|
||||||
scene,
|
scene,
|
||||||
realGroup,
|
realGroup,
|
||||||
@ -36,6 +47,6 @@ public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryH
|
|||||||
List.of(targetCount, timeLimit),
|
List.of(targetCount, timeLimit),
|
||||||
timeLimit, // Limit
|
timeLimit, // Limit
|
||||||
targetCount, // Goal
|
targetCount, // Goal
|
||||||
List.of(new KillMonsterCountTrigger(), new InTimeTrigger()));
|
challengeTriggers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package emu.grasscutter.game.dungeons.challenge.trigger;
|
|||||||
|
|
||||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||||
import emu.grasscutter.game.entity.EntityGadget;
|
import emu.grasscutter.game.entity.EntityGadget;
|
||||||
import emu.grasscutter.game.props.FightProperty;
|
|
||||||
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
||||||
|
|
||||||
public class GuardTrigger extends ChallengeTrigger {
|
public class GuardTrigger extends ChallengeTrigger {
|
||||||
@ -14,7 +13,12 @@ public class GuardTrigger extends ChallengeTrigger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onBegin(WorldChallenge challenge) {
|
public void onBegin(WorldChallenge challenge) {
|
||||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
|
challenge.setGuardEntity(
|
||||||
|
challenge.getScene().getEntityByConfigId(entityToProtectCFGId, challenge.getGroup().id));
|
||||||
|
lastSendPercent = challenge.getGuardEntityHpPercent();
|
||||||
|
challenge
|
||||||
|
.getScene()
|
||||||
|
.broadcastPacket(new PacketChallengeDataNotify(challenge, 2, lastSendPercent));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -22,9 +26,7 @@ public class GuardTrigger extends ChallengeTrigger {
|
|||||||
if (gadget.getConfigId() != entityToProtectCFGId) {
|
if (gadget.getConfigId() != entityToProtectCFGId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
|
var percent = challenge.getGuardEntityHpPercent();
|
||||||
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
|
|
||||||
int percent = (int) (curHp / maxHp);
|
|
||||||
|
|
||||||
if (percent != lastSendPercent) {
|
if (percent != lastSendPercent) {
|
||||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
|
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
|
||||||
|
@ -18,12 +18,6 @@ public class InTimeTrigger extends ChallengeTrigger {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckTimeout(WorldChallenge challenge) {
|
public void onCheckTimeout(WorldChallenge challenge) {
|
||||||
// In Tower challenges, time can run out without
|
|
||||||
// causing the challenge to fail. (Player just
|
|
||||||
// gets 0 stars when they ultimately finish.)
|
|
||||||
var dungeonManager = challenge.getScene().getDungeonManager();
|
|
||||||
if (dungeonManager != null && dungeonManager.isTowerDungeon()) return;
|
|
||||||
|
|
||||||
var current = challenge.getScene().getSceneTimeSeconds();
|
var current = challenge.getScene().getSceneTimeSeconds();
|
||||||
if (current - challenge.getStartedAt() > challenge.getTimeLimit()) {
|
if (current - challenge.getStartedAt() > challenge.getTimeLimit()) {
|
||||||
challenge.fail();
|
challenge.fail();
|
||||||
|
@ -6,22 +6,33 @@ import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
|||||||
|
|
||||||
public class KillMonsterTimeIncTrigger extends ChallengeTrigger {
|
public class KillMonsterTimeIncTrigger extends ChallengeTrigger {
|
||||||
|
|
||||||
private int increment;
|
private final int maxTime;
|
||||||
|
private final int increment;
|
||||||
|
|
||||||
public KillMonsterTimeIncTrigger(int increment) {
|
public KillMonsterTimeIncTrigger(int maxTime, int increment) {
|
||||||
|
this.maxTime = maxTime;
|
||||||
this.increment = increment;
|
this.increment = increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBegin(WorldChallenge challenge) {
|
public void onBegin(WorldChallenge challenge) {}
|
||||||
// challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 0,
|
|
||||||
// challenge.getScore().get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
|
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
|
||||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 0, increment));
|
var scene = challenge.getScene();
|
||||||
|
var elapsed = scene.getSceneTimeSeconds() - challenge.getStartedAt();
|
||||||
|
var timeLeft = challenge.getTimeLimit() - elapsed;
|
||||||
|
var increment = this.increment;
|
||||||
|
if (increment == 0) {
|
||||||
|
// Refresh time limit back to max
|
||||||
|
increment = maxTime - timeLeft;
|
||||||
|
} else if (maxTime < timeLeft + increment) {
|
||||||
|
// Don't add back more time than original limit
|
||||||
|
increment -= timeLeft + increment - maxTime;
|
||||||
|
}
|
||||||
challenge.setTimeLimit(challenge.getTimeLimit() + increment);
|
challenge.setTimeLimit(challenge.getTimeLimit() + increment);
|
||||||
|
scene.broadcastPacket(
|
||||||
|
new PacketChallengeDataNotify(
|
||||||
|
challenge, 2, timeLeft + increment + scene.getSceneTimeSeconds()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,12 @@ public class TowerResult extends BaseDungeonResult {
|
|||||||
@Override
|
@Override
|
||||||
protected void onProto(DungeonSettleNotifyOuterClass.DungeonSettleNotify.Builder builder) {
|
protected void onProto(DungeonSettleNotifyOuterClass.DungeonSettleNotify.Builder builder) {
|
||||||
var continueStatus = ContinueStateType.CONTINUE_STATE_TYPE_CAN_NOT_CONTINUE_VALUE;
|
var continueStatus = ContinueStateType.CONTINUE_STATE_TYPE_CAN_NOT_CONTINUE_VALUE;
|
||||||
if (challenge.isSuccess() && canJump) {
|
if (challenge.isSuccess()) {
|
||||||
continueStatus =
|
if (hasNextLevel) {
|
||||||
hasNextLevel
|
continueStatus = ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_LEVEL_VALUE;
|
||||||
? ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_LEVEL_VALUE
|
} else if (canJump) {
|
||||||
: ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_FLOOR_VALUE;
|
continueStatus = ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_FLOOR_VALUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var towerLevelEndNotify =
|
var towerLevelEndNotify =
|
||||||
|
@ -70,6 +70,11 @@ public abstract class EntityBaseGadget extends GameEntity {
|
|||||||
.setSourceEntityId(getId())
|
.setSourceEntityId(getId())
|
||||||
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
|
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
|
||||||
.setEventSource(getConfigId()));
|
.setEventSource(getConfigId()));
|
||||||
|
|
||||||
|
var challenge = getScene().getChallenge();
|
||||||
|
if (challenge != null && this instanceof EntityGadget gadget) {
|
||||||
|
challenge.onGadgetDamage(gadget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fillFightProps(ConfigEntityGadget configGadget) {
|
protected void fillFightProps(ConfigEntityGadget configGadget) {
|
||||||
|
@ -5,6 +5,7 @@ import emu.grasscutter.data.GameData;
|
|||||||
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
|
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
|
||||||
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
|
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
|
||||||
import emu.grasscutter.data.excels.GadgetData;
|
import emu.grasscutter.data.excels.GadgetData;
|
||||||
|
import emu.grasscutter.data.excels.monster.MonsterCurveData;
|
||||||
import emu.grasscutter.game.entity.gadget.*;
|
import emu.grasscutter.game.entity.gadget.*;
|
||||||
import emu.grasscutter.game.entity.gadget.platform.*;
|
import emu.grasscutter.game.entity.gadget.platform.*;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
@ -104,6 +105,25 @@ public class EntityGadget extends EntityBaseGadget {
|
|||||||
this.bornRot = this.getRotation().clone();
|
this.bornRot = this.getRotation().clone();
|
||||||
this.fillFightProps(configGadget);
|
this.fillFightProps(configGadget);
|
||||||
|
|
||||||
|
// Check if this gadget is the abyss defense objective's gadget.
|
||||||
|
// That doesn't have a level and defaults to having 5000 hp, so it dies in like 2 hits on 11-1.
|
||||||
|
// I'll forgive player skill issues and scale its hp up here.
|
||||||
|
// TODO: find out how its fight props are actually scaled
|
||||||
|
if (gadgetData.getJsonName().equals("SceneObj_Gear_Operator_Mamolu_Entity")) {
|
||||||
|
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(11);
|
||||||
|
if (curve != null) {
|
||||||
|
FightProperty[] hpProps = {
|
||||||
|
FightProperty.FIGHT_PROP_MAX_HP,
|
||||||
|
FightProperty.FIGHT_PROP_BASE_HP,
|
||||||
|
FightProperty.FIGHT_PROP_CUR_HP
|
||||||
|
};
|
||||||
|
for (var prop : hpProps) {
|
||||||
|
setFightProperty(
|
||||||
|
prop, this.getFightProperty(prop) * curve.getMultByProp("GROW_CURVE_HP_ENVIRONMENT"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (GameData.getGadgetMappingMap().containsKey(gadgetId)) {
|
if (GameData.getGadgetMappingMap().containsKey(gadgetId)) {
|
||||||
var controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController();
|
var controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController();
|
||||||
this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName));
|
this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName));
|
||||||
|
@ -25,6 +25,7 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
|
|||||||
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
|
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
|
||||||
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
|
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
|
||||||
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
||||||
|
import emu.grasscutter.net.proto.ServantInfoOuterClass.ServantInfo;
|
||||||
import emu.grasscutter.scripts.constants.EventType;
|
import emu.grasscutter.scripts.constants.EventType;
|
||||||
import emu.grasscutter.scripts.data.*;
|
import emu.grasscutter.scripts.data.*;
|
||||||
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
||||||
@ -49,6 +50,9 @@ public class EntityMonster extends GameEntity {
|
|||||||
@Getter private final Position bornPos;
|
@Getter private final Position bornPos;
|
||||||
@Getter private final int level;
|
@Getter private final int level;
|
||||||
@Getter private EntityWeapon weaponEntity;
|
@Getter private EntityWeapon weaponEntity;
|
||||||
|
@Getter private Map<Integer, EntityMonster> summonTagMap;
|
||||||
|
@Getter @Setter private int summonedTag;
|
||||||
|
@Getter @Setter private int ownerEntityId;
|
||||||
@Getter @Setter private int poseId;
|
@Getter @Setter private int poseId;
|
||||||
@Getter @Setter private int aiId = -1;
|
@Getter @Setter private int aiId = -1;
|
||||||
|
|
||||||
@ -67,6 +71,9 @@ public class EntityMonster extends GameEntity {
|
|||||||
this.bornPos = this.getPosition().clone();
|
this.bornPos = this.getPosition().clone();
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.playerOnBattle = new ArrayList<>();
|
this.playerOnBattle = new ArrayList<>();
|
||||||
|
this.summonTagMap = new HashMap<>();
|
||||||
|
this.summonedTag = 0;
|
||||||
|
this.ownerEntityId = 0;
|
||||||
|
|
||||||
if (GameData.getMonsterMappingMap().containsKey(this.getMonsterId())) {
|
if (GameData.getMonsterMappingMap().containsKey(this.getMonsterId())) {
|
||||||
this.configEntityMonster =
|
this.configEntityMonster =
|
||||||
@ -76,6 +83,17 @@ public class EntityMonster extends GameEntity {
|
|||||||
this.configEntityMonster = null;
|
this.configEntityMonster = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.configEntityMonster != null
|
||||||
|
&& this.configEntityMonster.getCombat() != null
|
||||||
|
&& this.configEntityMonster.getCombat().getSummon() != null
|
||||||
|
&& this.configEntityMonster.getCombat().getSummon().getSummonTags() != null) {
|
||||||
|
this.configEntityMonster
|
||||||
|
.getCombat()
|
||||||
|
.getSummon()
|
||||||
|
.getSummonTags()
|
||||||
|
.forEach(t -> this.summonTagMap.put(t.getSummonTag(), null));
|
||||||
|
}
|
||||||
|
|
||||||
// Monster weapon
|
// Monster weapon
|
||||||
if (getMonsterWeaponId() > 0) {
|
if (getMonsterWeaponId() > 0) {
|
||||||
this.weaponEntity = new EntityWeapon(scene, getMonsterWeaponId());
|
this.weaponEntity = new EntityWeapon(scene, getMonsterWeaponId());
|
||||||
@ -204,7 +222,9 @@ public class EntityMonster extends GameEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onTick(int sceneTime) {
|
||||||
|
super.onTick(sceneTime);
|
||||||
|
|
||||||
// Lua event
|
// Lua event
|
||||||
getScene()
|
getScene()
|
||||||
.getScriptManager()
|
.getScriptManager()
|
||||||
@ -316,6 +336,11 @@ public class EntityMonster extends GameEntity {
|
|||||||
this.getMonsterData().getType().getValue());
|
this.getMonsterData().getType().getValue());
|
||||||
scene.triggerDungeonEvent(
|
scene.triggerDungeonEvent(
|
||||||
DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
||||||
|
|
||||||
|
// If this entity spawned servants, kill those too.
|
||||||
|
summonTagMap.values().stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(entity -> scene.killEntity(entity, killerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recalcStats() {
|
public void recalcStats() {
|
||||||
@ -355,6 +380,28 @@ public class EntityMonster extends GameEntity {
|
|||||||
+ (this.getFightProperty(c.getBase())
|
+ (this.getFightProperty(c.getBase())
|
||||||
* (1f + this.getFightProperty(c.getPercent())))));
|
* (1f + this.getFightProperty(c.getPercent())))));
|
||||||
|
|
||||||
|
// If in tower, scale max hp by
|
||||||
|
// +50%: Floors 3 – 7
|
||||||
|
// +100%: Floors 8 – 11
|
||||||
|
// +150%: Floor 12
|
||||||
|
var dungeonManager = getScene().getDungeonManager();
|
||||||
|
var towerManager = getScene().getPlayers().get(0).getTowerManager();
|
||||||
|
if (dungeonManager != null && dungeonManager.isTowerDungeon() && towerManager != null) {
|
||||||
|
var floor = towerManager.getCurrentFloorNumber();
|
||||||
|
float additionalScaleFactor = 0f;
|
||||||
|
if (floor >= 12) {
|
||||||
|
additionalScaleFactor = 1.5f;
|
||||||
|
} else if (floor >= 8) {
|
||||||
|
additionalScaleFactor = 1.f;
|
||||||
|
} else if (floor >= 3) {
|
||||||
|
additionalScaleFactor = .5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setFightProperty(
|
||||||
|
FightProperty.FIGHT_PROP_MAX_HP,
|
||||||
|
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * (1 + additionalScaleFactor));
|
||||||
|
}
|
||||||
|
|
||||||
// Set current hp
|
// Set current hp
|
||||||
this.setFightProperty(
|
this.setFightProperty(
|
||||||
FightProperty.FIGHT_PROP_CUR_HP,
|
FightProperty.FIGHT_PROP_CUR_HP,
|
||||||
@ -365,14 +412,17 @@ public class EntityMonster extends GameEntity {
|
|||||||
public SceneEntityInfo toProto() {
|
public SceneEntityInfo toProto() {
|
||||||
var data = this.getMonsterData();
|
var data = this.getMonsterData();
|
||||||
|
|
||||||
|
var aiInfo =
|
||||||
|
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto());
|
||||||
|
if (ownerEntityId != 0) {
|
||||||
|
aiInfo.setServantInfo(ServantInfo.newBuilder().setMasterEntityId(ownerEntityId));
|
||||||
|
}
|
||||||
|
|
||||||
var authority =
|
var authority =
|
||||||
EntityAuthorityInfo.newBuilder()
|
EntityAuthorityInfo.newBuilder()
|
||||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||||
.setAiInfo(
|
.setAiInfo(aiInfo)
|
||||||
SceneEntityAiInfo.newBuilder()
|
|
||||||
.setIsAiOpen(true)
|
|
||||||
.setBornPos(this.getBornPos().toProto()))
|
|
||||||
.setBornPos(this.getBornPos().toProto())
|
.setBornPos(this.getBornPos().toProto())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -403,7 +453,10 @@ public class EntityMonster extends GameEntity {
|
|||||||
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
||||||
.setPoseId(this.getPoseId())
|
.setPoseId(this.getPoseId())
|
||||||
.setBlockId(this.getScene().getId())
|
.setBlockId(this.getScene().getId())
|
||||||
|
.setSummonedTag(this.summonedTag)
|
||||||
|
.setOwnerEntityId(this.ownerEntityId)
|
||||||
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
||||||
|
summonTagMap.forEach((k, v) -> monsterInfo.putSummonTagMap(k, v == null ? 0 : 1));
|
||||||
|
|
||||||
if (this.metaMonster != null) {
|
if (this.metaMonster != null) {
|
||||||
if (this.metaMonster.special_name_id != 0) {
|
if (this.metaMonster.special_name_id != 0) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package emu.grasscutter.game.entity;
|
package emu.grasscutter.game.entity;
|
||||||
|
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.data.binout.*;
|
||||||
import emu.grasscutter.game.ability.*;
|
import emu.grasscutter.game.ability.*;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.props.*;
|
import emu.grasscutter.game.props.*;
|
||||||
@ -32,6 +33,8 @@ public abstract class GameEntity {
|
|||||||
@Getter @Setter private int lastMoveReliableSeq;
|
@Getter @Setter private int lastMoveReliableSeq;
|
||||||
|
|
||||||
@Getter @Setter private boolean lockHP;
|
@Getter @Setter private boolean lockHP;
|
||||||
|
private boolean limbo;
|
||||||
|
private float limboHpThreshold;
|
||||||
|
|
||||||
@Setter(AccessLevel.PROTECTED)
|
@Setter(AccessLevel.PROTECTED)
|
||||||
@Getter
|
@Getter
|
||||||
@ -110,6 +113,21 @@ public abstract class GameEntity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setLimbo(float hpThreshold) {
|
||||||
|
limbo = true;
|
||||||
|
limboHpThreshold = hpThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAddAbilityModifier(AbilityModifier data) {
|
||||||
|
// Set limbo state (invulnerability at a certain HP threshold)
|
||||||
|
// if ability modifier calls for it
|
||||||
|
if (data.state == AbilityModifier.State.Limbo
|
||||||
|
&& data.properties != null
|
||||||
|
&& data.properties.Actor_HpThresholdRatio > .0f) {
|
||||||
|
this.setLimbo(data.properties.Actor_HpThresholdRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected MotionInfo getMotionInfo() {
|
protected MotionInfo getMotionInfo() {
|
||||||
return MotionInfo.newBuilder()
|
return MotionInfo.newBuilder()
|
||||||
.setPos(this.getPosition().toProto())
|
.setPos(this.getPosition().toProto())
|
||||||
@ -167,11 +185,26 @@ public abstract class GameEntity {
|
|||||||
return; // If the event is canceled, do not damage the entity.
|
return; // If the event is canceled, do not damage the entity.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float effectiveDamage = 0;
|
||||||
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||||
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
|
if (limbo) {
|
||||||
// Add negative HP to the current HP property.
|
float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||||
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
|
float curRatio = curHp / maxHp;
|
||||||
|
if (curRatio > limboHpThreshold) {
|
||||||
|
// OK if this hit takes HP below threshold.
|
||||||
|
effectiveDamage = event.getDamage();
|
||||||
}
|
}
|
||||||
|
if (effectiveDamage >= curHp && limboHpThreshold > .0f) {
|
||||||
|
// Don't let entity die while in limbo.
|
||||||
|
effectiveDamage = curHp - 1;
|
||||||
|
}
|
||||||
|
} else if (curHp != Float.POSITIVE_INFINITY && !lockHP
|
||||||
|
|| lockHP && curHp <= event.getDamage()) {
|
||||||
|
effectiveDamage = event.getDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add negative HP to the current HP property.
|
||||||
|
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -effectiveDamage);
|
||||||
|
|
||||||
this.lastAttackType = attackType;
|
this.lastAttackType = attackType;
|
||||||
this.checkIfDead();
|
this.checkIfDead();
|
||||||
|
@ -9,9 +9,10 @@ import emu.grasscutter.database.DatabaseHelper;
|
|||||||
import emu.grasscutter.game.avatar.Avatar;
|
import emu.grasscutter.game.avatar.Avatar;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.props.SceneType;
|
import emu.grasscutter.game.props.SceneType;
|
||||||
import emu.grasscutter.net.proto.HomeAvatarTalkFinishInfoOuterClass;
|
import emu.grasscutter.net.proto.HomeAvatarTalkFinishInfoOuterClass.HomeAvatarTalkFinishInfo;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSets;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -36,6 +37,10 @@ public class GameHome {
|
|||||||
|| sceneData.getSceneType() == SceneType.SCENE_HOME_ROOM)
|
|| sceneData.getSceneType() == SceneType.SCENE_HOME_ROOM)
|
||||||
.map(SceneData::getId)
|
.map(SceneData::getId)
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
public static final Set<Integer> HOME_MODULE_IDS =
|
||||||
|
GameData.getHomeWorldModuleDataMap().isEmpty()
|
||||||
|
? IntSets.fromTo(1, 6)
|
||||||
|
: GameData.getHomeWorldModuleDataMap().keySet();
|
||||||
|
|
||||||
@Id String id;
|
@Id String id;
|
||||||
|
|
||||||
@ -181,6 +186,7 @@ public class GameHome {
|
|||||||
|
|
||||||
public void onOwnerLogin(Player player) {
|
public void onOwnerLogin(Player player) {
|
||||||
this.player = player; // update player pointer. (prevent offline player from sending packet)
|
this.player = player; // update player pointer. (prevent offline player from sending packet)
|
||||||
|
this.fixModuleIdIfInvalid();
|
||||||
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
|
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
|
||||||
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
|
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
|
||||||
player.getSession().send(new PacketHomeComfortInfoNotify(player));
|
player.getSession().send(new PacketHomeComfortInfoNotify(player));
|
||||||
@ -194,6 +200,35 @@ public class GameHome {
|
|||||||
player.getSession().send(new PacketHomeResourceNotify(player));
|
player.getSession().send(new PacketHomeResourceNotify(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fixModuleIdIfInvalid() {
|
||||||
|
if (this.player.hasSentLoginPackets() || this.player.getRealmList() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player
|
||||||
|
.getRealmList()
|
||||||
|
.removeIf(integer -> !HOME_MODULE_IDS.contains(integer)); // Delete invalid module ids.
|
||||||
|
|
||||||
|
if (this.player.getRealmList().isEmpty()) {
|
||||||
|
this.player.setRealmList(null);
|
||||||
|
this.player.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.player.getCurrentRealmId() <= 0 || !this.player.getCurHomeWorld().isRealmIdValid()) {
|
||||||
|
int firstRId = this.player.getRealmList().iterator().next();
|
||||||
|
this.player.setCurrentRealmId(firstRId);
|
||||||
|
this.player.save();
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.info(
|
||||||
|
"Set player {}'s current realm id to {} cuz the id is invalid.",
|
||||||
|
this.player.getUid(),
|
||||||
|
firstRId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.getCurHomeWorld().refreshModuleManager(); // Apply module id fix.
|
||||||
|
}
|
||||||
|
|
||||||
public void onPlayerChangedAvatarCostume(Avatar avatar) {
|
public void onPlayerChangedAvatarCostume(Avatar avatar) {
|
||||||
var world = this.player.getServer().getHomeWorldOrCreate(this.player);
|
var world = this.player.getServer().getHomeWorldOrCreate(this.player);
|
||||||
world.broadcastPacket(
|
world.broadcastPacket(
|
||||||
@ -239,8 +274,7 @@ public class GameHome {
|
|||||||
return this.finishedTalkIdMap.get(avatarId);
|
return this.finishedTalkIdMap.get(avatarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<HomeAvatarTalkFinishInfoOuterClass.HomeAvatarTalkFinishInfo>
|
public List<HomeAvatarTalkFinishInfo> toAvatarTalkFinishInfoProto() {
|
||||||
toAvatarTalkFinishInfoProto() {
|
|
||||||
if (this.finishedTalkIdMap == null) {
|
if (this.finishedTalkIdMap == null) {
|
||||||
this.finishedTalkIdMap = new HashMap<>();
|
this.finishedTalkIdMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
@ -248,7 +282,7 @@ public class GameHome {
|
|||||||
return this.finishedTalkIdMap.entrySet().stream()
|
return this.finishedTalkIdMap.entrySet().stream()
|
||||||
.map(
|
.map(
|
||||||
e -> {
|
e -> {
|
||||||
return HomeAvatarTalkFinishInfoOuterClass.HomeAvatarTalkFinishInfo.newBuilder()
|
return HomeAvatarTalkFinishInfo.newBuilder()
|
||||||
.setAvatarId(e.getKey())
|
.setAvatarId(e.getKey())
|
||||||
.addAllFinishTalkIdList(e.getValue())
|
.addAllFinishTalkIdList(e.getValue())
|
||||||
.build();
|
.build();
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
package emu.grasscutter.game.home;
|
package emu.grasscutter.game.home;
|
||||||
|
|
||||||
import com.github.davidmoten.guavamini.Lists;
|
import com.github.davidmoten.guavamini.Lists;
|
||||||
|
import emu.grasscutter.game.home.suite.HomeSuiteItem;
|
||||||
import emu.grasscutter.game.home.suite.event.HomeAvatarRewardEvent;
|
import emu.grasscutter.game.home.suite.event.HomeAvatarRewardEvent;
|
||||||
import emu.grasscutter.game.home.suite.event.HomeAvatarSummonEvent;
|
import emu.grasscutter.game.home.suite.event.HomeAvatarSummonEvent;
|
||||||
import emu.grasscutter.game.home.suite.event.SuiteEventType;
|
import emu.grasscutter.game.home.suite.event.SuiteEventType;
|
||||||
import emu.grasscutter.game.inventory.GameItem;
|
import emu.grasscutter.game.inventory.GameItem;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.proto.HomeAvatarRewardEventNotifyOuterClass;
|
import emu.grasscutter.net.proto.HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify;
|
||||||
import emu.grasscutter.net.proto.HomeAvatarSummonAllEventNotifyOuterClass;
|
import emu.grasscutter.net.proto.HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify;
|
||||||
import emu.grasscutter.net.proto.RetcodeOuterClass;
|
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||||
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonAllEventNotify;
|
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonAllEventNotify;
|
||||||
import emu.grasscutter.utils.Either;
|
import emu.grasscutter.utils.Either;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
@ -24,8 +26,8 @@ public class HomeModuleManager {
|
|||||||
final HomeWorld homeWorld;
|
final HomeWorld homeWorld;
|
||||||
final GameHome home;
|
final GameHome home;
|
||||||
final int moduleId;
|
final int moduleId;
|
||||||
final HomeScene outdoor;
|
@Nullable final HomeScene outdoor;
|
||||||
HomeScene indoor;
|
@Nullable HomeScene indoor;
|
||||||
final List<HomeAvatarRewardEvent> rewardEvents;
|
final List<HomeAvatarRewardEvent> rewardEvents;
|
||||||
final List<HomeAvatarSummonEvent> summonEvents;
|
final List<HomeAvatarSummonEvent> summonEvents;
|
||||||
|
|
||||||
@ -45,8 +47,14 @@ public class HomeModuleManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.outdoor != null) {
|
||||||
this.outdoor.onTick();
|
this.outdoor.onTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.indoor != null) {
|
||||||
this.indoor.onTick();
|
this.indoor.onTick();
|
||||||
|
}
|
||||||
|
|
||||||
this.summonEvents.removeIf(HomeAvatarSummonEvent::isTimeOver);
|
this.summonEvents.removeIf(HomeAvatarSummonEvent::isTimeOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +75,7 @@ public class HomeModuleManager {
|
|||||||
this.rewardEvents.clear();
|
this.rewardEvents.clear();
|
||||||
var allBlockItems =
|
var allBlockItems =
|
||||||
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.map(HomeSceneItem::getBlockItems)
|
.map(HomeSceneItem::getBlockItems)
|
||||||
.map(Map::values)
|
.map(Map::values)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
@ -114,6 +123,7 @@ public class HomeModuleManager {
|
|||||||
private void cancelSummonEventsIfAvatarLeave() {
|
private void cancelSummonEventsIfAvatarLeave() {
|
||||||
var avatars =
|
var avatars =
|
||||||
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.map(HomeSceneItem::getBlockItems)
|
.map(HomeSceneItem::getBlockItems)
|
||||||
.map(Map::values)
|
.map(Map::values)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
@ -127,16 +137,16 @@ public class HomeModuleManager {
|
|||||||
|
|
||||||
public Either<List<GameItem>, Integer> claimAvatarRewards(int eventId) {
|
public Either<List<GameItem>, Integer> claimAvatarRewards(int eventId) {
|
||||||
if (this.rewardEvents.isEmpty()) {
|
if (this.rewardEvents.isEmpty()) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
return Either.right(Retcode.RET_FAIL_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
var event = this.rewardEvents.remove(0);
|
var event = this.rewardEvents.remove(0);
|
||||||
if (event.getEventId() != eventId) {
|
if (event.getEventId() != eventId) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
return Either.right(Retcode.RET_FAIL_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.homeOwner.getHome().onClaimAvatarRewards(eventId)) {
|
if (!this.homeOwner.getHome().onClaimAvatarRewards(eventId)) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
|
return Either.right(Retcode.RET_FAIL_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Either.left(event.giveRewards());
|
return Either.left(event.giveRewards());
|
||||||
@ -144,32 +154,34 @@ public class HomeModuleManager {
|
|||||||
|
|
||||||
public Either<HomeAvatarSummonEvent, Integer> fireAvatarSummonEvent(
|
public Either<HomeAvatarSummonEvent, Integer> fireAvatarSummonEvent(
|
||||||
Player owner, int avatarId, int guid, int suiteId) {
|
Player owner, int avatarId, int guid, int suiteId) {
|
||||||
var targetSuite =
|
HomeSuiteItem targetSuite = null;
|
||||||
((HomeScene) owner.getScene())
|
if (owner.getScene() instanceof HomeScene homeScene) {
|
||||||
.getSceneItem().getBlockItems().values().stream()
|
targetSuite =
|
||||||
|
homeScene.getSceneItem().getBlockItems().values().stream()
|
||||||
.map(HomeBlockItem::getSuiteList)
|
.map(HomeBlockItem::getSuiteList)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.filter(suite -> suite.getGuid() == guid)
|
.filter(suite -> suite.getGuid() == guid)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isInRewardEvent(avatarId)) {
|
if (this.isInRewardEvent(avatarId)) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_DUPLICATE_AVATAR_VALUE);
|
return Either.right(Retcode.RET_DUPLICATE_AVATAR_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rewardEvents.stream().anyMatch(event -> event.getGuid() == guid)) {
|
if (this.rewardEvents.stream().anyMatch(event -> event.getGuid() == guid)) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_FURNITURE_GUID_ERROR_VALUE);
|
return Either.right(Retcode.RET_HOME_FURNITURE_GUID_ERROR_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.summonEvents.removeIf(event -> event.getGuid() == guid || event.getAvatarId() == avatarId);
|
this.summonEvents.removeIf(event -> event.getGuid() == guid || event.getAvatarId() == avatarId);
|
||||||
|
|
||||||
if (targetSuite == null) {
|
if (targetSuite == null) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
return Either.right(Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventData = SuiteEventType.HOME_AVATAR_SUMMON_EVENT.getEventDataFrom(avatarId, suiteId);
|
var eventData = SuiteEventType.HOME_AVATAR_SUMMON_EVENT.getEventDataFrom(avatarId, suiteId);
|
||||||
if (eventData == null) {
|
if (eventData == null) {
|
||||||
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
return Either.right(Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
var event =
|
var event =
|
||||||
@ -184,8 +196,8 @@ public class HomeModuleManager {
|
|||||||
this.summonEvents.removeIf(event -> event.getEventId() == eventId);
|
this.summonEvents.removeIf(event -> event.getEventId() == eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify toRewardEventProto() {
|
public HomeAvatarRewardEventNotify toRewardEventProto() {
|
||||||
var notify = HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify.newBuilder();
|
var notify = HomeAvatarRewardEventNotify.newBuilder();
|
||||||
if (!this.rewardEvents.isEmpty()) {
|
if (!this.rewardEvents.isEmpty()) {
|
||||||
notify.setRewardEvent(this.rewardEvents.get(0).toProto()).setIsEventTrigger(true);
|
notify.setRewardEvent(this.rewardEvents.get(0).toProto()).setIsEventTrigger(true);
|
||||||
|
|
||||||
@ -198,9 +210,8 @@ public class HomeModuleManager {
|
|||||||
return notify.build();
|
return notify.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify
|
public HomeAvatarSummonAllEventNotify toSummonEventProto() {
|
||||||
toSummonEventProto() {
|
return HomeAvatarSummonAllEventNotify.newBuilder()
|
||||||
return HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify.newBuilder()
|
|
||||||
.addAllSummonEventList(
|
.addAllSummonEventList(
|
||||||
this.summonEvents.stream().map(HomeAvatarSummonEvent::toProto).toList())
|
this.summonEvents.stream().map(HomeAvatarSummonEvent::toProto).toList())
|
||||||
.build();
|
.build();
|
||||||
@ -210,12 +221,12 @@ public class HomeModuleManager {
|
|||||||
return this.rewardEvents.stream().anyMatch(e -> e.getAvatarId() == avatarId);
|
return this.rewardEvents.stream().anyMatch(e -> e.getAvatarId() == avatarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HomeSceneItem getOutdoorSceneItem() {
|
@Nullable public HomeSceneItem getOutdoorSceneItem() {
|
||||||
return this.outdoor.getSceneItem();
|
return this.outdoor == null ? null : this.outdoor.getSceneItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HomeSceneItem getIndoorSceneItem() {
|
@Nullable public HomeSceneItem getIndoorSceneItem() {
|
||||||
return this.indoor.getSceneItem();
|
return this.indoor == null ? null : this.indoor.getSceneItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSetModule() {
|
public void onSetModule() {
|
||||||
@ -223,8 +234,14 @@ public class HomeModuleManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.outdoor != null) {
|
||||||
this.outdoor.addEntities(this.getOutdoorSceneItem().getAnimals(this.outdoor));
|
this.outdoor.addEntities(this.getOutdoorSceneItem().getAnimals(this.outdoor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.indoor != null) {
|
||||||
this.indoor.addEntities(this.getIndoorSceneItem().getAnimals(this.indoor));
|
this.indoor.addEntities(this.getIndoorSceneItem().getAnimals(this.indoor));
|
||||||
|
}
|
||||||
|
|
||||||
this.fireAllAvatarRewardEvents();
|
this.fireAllAvatarRewardEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +250,12 @@ public class HomeModuleManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.outdoor != null) {
|
||||||
this.outdoor.getEntities().clear();
|
this.outdoor.getEntities().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.indoor != null) {
|
||||||
this.indoor.getEntities().clear();
|
this.indoor.getEntities().clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package emu.grasscutter.game.home;
|
|||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.game.entity.EntityTeam;
|
import emu.grasscutter.game.entity.EntityTeam;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.world.Scene;
|
|
||||||
import emu.grasscutter.game.world.World;
|
import emu.grasscutter.game.world.World;
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.proto.ChatInfoOuterClass;
|
import emu.grasscutter.net.proto.ChatInfoOuterClass;
|
||||||
@ -13,6 +12,7 @@ import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
|
|||||||
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
|
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -66,7 +66,7 @@ public class HomeWorld extends World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRealmIdValid() {
|
public boolean isRealmIdValid() {
|
||||||
return this.getHost().getCurrentRealmId() > 0;
|
return this.getSceneById(this.getHost().getCurrentRealmId() + 2000) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,11 +147,13 @@ public class HomeWorld extends World {
|
|||||||
player.setWorld(null);
|
player.setWorld(null);
|
||||||
|
|
||||||
// Remove from scene
|
// Remove from scene
|
||||||
Scene scene = this.getSceneById(player.getSceneId());
|
var scene = this.getSceneById(player.getSceneId());
|
||||||
|
if (scene != null) {
|
||||||
scene.removePlayer(player);
|
scene.removePlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
// Info packet for other players
|
// Info packet for other players
|
||||||
if (this.getPlayers().size() > 0) {
|
if (!this.getPlayers().isEmpty()) {
|
||||||
this.updatePlayerInfos(player);
|
this.updatePlayerInfos(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +169,7 @@ public class HomeWorld extends World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HomeScene getSceneById(int sceneId) {
|
@Nullable public HomeScene getSceneById(int sceneId) {
|
||||||
var scene = this.getScenes().get(sceneId);
|
var scene = this.getScenes().get(sceneId);
|
||||||
if (scene instanceof HomeScene homeScene) {
|
if (scene instanceof HomeScene homeScene) {
|
||||||
return homeScene;
|
return homeScene;
|
||||||
|
@ -139,11 +139,15 @@ public class HomeWorldMPSystem extends BaseGameSystem {
|
|||||||
|
|
||||||
int realmId = 2000 + owner.getCurrentRealmId();
|
int realmId = 2000 + owner.getCurrentRealmId();
|
||||||
var item = targetHome.getHomeSceneItem(realmId);
|
var item = targetHome.getHomeSceneItem(realmId);
|
||||||
|
var scene = world.getSceneById(realmId);
|
||||||
targetHome.save();
|
targetHome.save();
|
||||||
var pos =
|
|
||||||
toSafe
|
Position pos;
|
||||||
? world.getSceneById(realmId).getScriptManager().getConfig().born_pos
|
if (scene != null) {
|
||||||
: item.getBornPos();
|
pos = toSafe ? scene.getScriptManager().getConfig().born_pos : item.getBornPos();
|
||||||
|
} else {
|
||||||
|
pos = item.getBornPos();
|
||||||
|
}
|
||||||
|
|
||||||
if (teleportPoint != 0) {
|
if (teleportPoint != 0) {
|
||||||
var target = item.getTeleportPointPos(teleportPoint);
|
var target = item.getTeleportPointPos(teleportPoint);
|
||||||
|
@ -259,8 +259,14 @@ public class EnergyManager extends BasePlayerManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also reference AvatarSkillData in case the burst gets a different skill ID
|
||||||
|
// when the avatar is in a different state. For example, Wanderer's burst is
|
||||||
|
// 10755 usually but when he floats, it becomes 10753.
|
||||||
|
var skillData = GameData.getAvatarSkillDataMap().get(skillId);
|
||||||
|
|
||||||
// If the cast skill was a burst, consume energy.
|
// If the cast skill was a burst, consume energy.
|
||||||
if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
|
if ((avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill())
|
||||||
|
|| (skillData != null && skillData.getCostElemVal() > 0)) {
|
||||||
avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
|
avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setOpenState(int openState, int value, boolean sendNotify) {
|
private void setOpenState(int openState, int value, boolean sendNotify) {
|
||||||
int previousValue = this.player.getOpenStates().getOrDefault(openState, 0);
|
int previousValue = this.player.getOpenStates().getOrDefault(openState, -1 /* non-existent */);
|
||||||
|
|
||||||
if (value != previousValue) {
|
if (value != previousValue) {
|
||||||
this.player.getOpenStates().put(openState, value);
|
this.player.getOpenStates().put(openState, value);
|
||||||
|
@ -29,6 +29,11 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
return this.getTowerData().currentFloorId;
|
return this.getTowerData().currentFloorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** floor number: 1 - 12 * */
|
||||||
|
public int getCurrentFloorNumber() {
|
||||||
|
return GameData.getTowerFloorDataMap().get(getCurrentFloorId()).getFloorIndex();
|
||||||
|
}
|
||||||
|
|
||||||
public int getCurrentLevelId() {
|
public int getCurrentLevelId() {
|
||||||
return this.getTowerData().currentLevelId + this.getTowerData().currentLevel;
|
return this.getTowerData().currentLevelId + this.getTowerData().currentLevel;
|
||||||
}
|
}
|
||||||
@ -40,7 +45,7 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
|
|
||||||
public void onTick() {
|
public void onTick() {
|
||||||
var challenge = player.getScene().getChallenge();
|
var challenge = player.getScene().getChallenge();
|
||||||
if (challenge == null || !challenge.inProgress()) return;
|
if (!inProgress || challenge == null || !challenge.inProgress()) return;
|
||||||
|
|
||||||
// Check star conditions and notify client if any failed.
|
// Check star conditions and notify client if any failed.
|
||||||
int stars = getCurLevelStars();
|
int stars = getCurLevelStars();
|
||||||
@ -93,8 +98,17 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
player.getTeamManager().setupTemporaryTeam(towerTeams);
|
player.getTeamManager().setupTemporaryTeam(towerTeams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TowerLevelData getCurrentTowerLevelDataMap() {
|
||||||
|
return GameData.getTowerLevelDataMap().get(getCurrentLevelId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentMonsterLevel() {
|
||||||
|
// monsterLevel given in TowerLevelExcelConfigData.json is off by one.
|
||||||
|
return getCurrentTowerLevelDataMap().getMonsterLevel() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public void enterLevel(int enterPointId) {
|
public void enterLevel(int enterPointId) {
|
||||||
var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId());
|
var levelData = getCurrentTowerLevelDataMap();
|
||||||
|
|
||||||
var dungeonId = levelData.getDungeonId();
|
var dungeonId = levelData.getDungeonId();
|
||||||
|
|
||||||
@ -140,7 +154,7 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId());
|
var levelData = getCurrentTowerLevelDataMap();
|
||||||
// 0-based indexing. "star" = 0 means checking for 1-star conditions.
|
// 0-based indexing. "star" = 0 means checking for 1-star conditions.
|
||||||
int star;
|
int star;
|
||||||
for (star = 2; star >= 0; star--) {
|
for (star = 2; star >= 0; star--) {
|
||||||
@ -153,8 +167,11 @@ public class TowerManager extends BasePlayerManager {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (cond == TowerLevelData.TowerCondType.TOWER_COND_LEFT_HP_GREATER_THAN) {
|
} else if (cond == TowerLevelData.TowerCondType.TOWER_COND_LEFT_HP_GREATER_THAN) {
|
||||||
// TODO: Check monolith health
|
var params = levelData.getHpCond(star);
|
||||||
|
var hpPercent = challenge.getGuardEntityHpPercent();
|
||||||
|
if (hpPercent >= params.getMinimumHpPercentage()) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Grasscutter.getLogger()
|
Grasscutter.getLogger()
|
||||||
.error(
|
.error(
|
||||||
|
@ -600,7 +600,7 @@ public class Scene {
|
|||||||
// Should be OK to check only player 0,
|
// Should be OK to check only player 0,
|
||||||
// as no other players could enter Tower
|
// as no other players could enter Tower
|
||||||
var towerManager = getPlayers().get(0).getTowerManager();
|
var towerManager = getPlayers().get(0).getTowerManager();
|
||||||
if (towerManager != null) {
|
if (towerManager != null && towerManager.isInProgress()) {
|
||||||
towerManager.onTick();
|
towerManager.onTick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,6 +767,19 @@ public class Scene {
|
|||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getLevelForMonster(int configId, int defaultLevel) {
|
||||||
|
if (getDungeonManager() != null) {
|
||||||
|
return getDungeonManager().getLevelForMonster(configId);
|
||||||
|
} else if (getWorld().getWorldLevel() > 0) {
|
||||||
|
var worldLevelData = GameData.getWorldLevelDataMap().get(getWorld().getWorldLevel());
|
||||||
|
|
||||||
|
if (worldLevelData != null) {
|
||||||
|
return worldLevelData.getMonsterLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public void checkNpcGroup() {
|
public void checkNpcGroup() {
|
||||||
Set<SceneNpcBornEntry> npcBornEntries = ConcurrentHashMap.newKeySet();
|
Set<SceneNpcBornEntry> npcBornEntries = ConcurrentHashMap.newKeySet();
|
||||||
for (Player player : this.getPlayers()) {
|
for (Player player : this.getPlayers()) {
|
||||||
|
@ -5,14 +5,19 @@ import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportTy
|
|||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
||||||
import emu.grasscutter.game.entity.*;
|
import emu.grasscutter.game.entity.EntityTeam;
|
||||||
|
import emu.grasscutter.game.entity.EntityWorld;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.player.Player.SceneLoadState;
|
import emu.grasscutter.game.player.Player.SceneLoadState;
|
||||||
import emu.grasscutter.game.props.*;
|
import emu.grasscutter.game.props.EnterReason;
|
||||||
|
import emu.grasscutter.game.props.EntityIdType;
|
||||||
|
import emu.grasscutter.game.props.PlayerProperty;
|
||||||
|
import emu.grasscutter.game.props.SceneType;
|
||||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||||
import emu.grasscutter.game.world.data.TeleportProperties;
|
import emu.grasscutter.game.world.data.TeleportProperties;
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.*;
|
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.SystemHint;
|
||||||
|
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.SystemHintType;
|
||||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||||
import emu.grasscutter.scripts.data.SceneConfig;
|
import emu.grasscutter.scripts.data.SceneConfig;
|
||||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||||
@ -21,10 +26,20 @@ import emu.grasscutter.server.game.GameServer;
|
|||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.ConversionUtils;
|
import emu.grasscutter.utils.ConversionUtils;
|
||||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import java.util.*;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||||
import java.util.concurrent.*;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import lombok.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.val;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class World implements Iterable<Player> {
|
public class World implements Iterable<Player> {
|
||||||
@ -124,7 +139,7 @@ public class World implements Iterable<Player> {
|
|||||||
* @param sceneId The scene ID.
|
* @param sceneId The scene ID.
|
||||||
* @return The scene.
|
* @return The scene.
|
||||||
*/
|
*/
|
||||||
public Scene getSceneById(int sceneId) {
|
@Nullable public Scene getSceneById(int sceneId) {
|
||||||
// Get scene normally
|
// Get scene normally
|
||||||
var scene = this.getScenes().get(sceneId);
|
var scene = this.getScenes().get(sceneId);
|
||||||
if (scene != null) {
|
if (scene != null) {
|
||||||
@ -152,7 +167,7 @@ public class World implements Iterable<Player> {
|
|||||||
* @param idType The entity type.
|
* @param idType The entity type.
|
||||||
* @return The next entity ID.
|
* @return The next entity ID.
|
||||||
*/
|
*/
|
||||||
public int getNextEntityId(EntityIdType idType) {
|
public synchronized int getNextEntityId(EntityIdType idType) {
|
||||||
return (idType.getId() << 24) + ++this.nextEntityId;
|
return (idType.getId() << 24) + ++this.nextEntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ public class SceneScriptManager {
|
|||||||
/** current triggers controlled by RefreshGroup */
|
/** current triggers controlled by RefreshGroup */
|
||||||
private final Map<Integer, Set<SceneTrigger>> currentTriggers;
|
private final Map<Integer, Set<SceneTrigger>> currentTriggers;
|
||||||
|
|
||||||
|
private final Set<SceneTrigger> ongoingTriggers;
|
||||||
private final Map<String, Set<SceneTrigger>> triggersByGroupScene;
|
private final Map<String, Set<SceneTrigger>> triggersByGroupScene;
|
||||||
private final Map<Integer, Set<Pair<String, Integer>>> activeGroupTimers;
|
private final Map<Integer, Set<Pair<String, Integer>>> activeGroupTimers;
|
||||||
private final Map<String, AtomicInteger> triggerInvocations;
|
private final Map<String, AtomicInteger> triggerInvocations;
|
||||||
@ -76,6 +77,7 @@ public class SceneScriptManager {
|
|||||||
public SceneScriptManager(Scene scene) {
|
public SceneScriptManager(Scene scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.currentTriggers = new ConcurrentHashMap<>();
|
this.currentTriggers = new ConcurrentHashMap<>();
|
||||||
|
this.ongoingTriggers = ConcurrentHashMap.newKeySet();
|
||||||
this.triggersByGroupScene = new ConcurrentHashMap<>();
|
this.triggersByGroupScene = new ConcurrentHashMap<>();
|
||||||
this.activeGroupTimers = new ConcurrentHashMap<>();
|
this.activeGroupTimers = new ConcurrentHashMap<>();
|
||||||
this.triggerInvocations = new ConcurrentHashMap<>();
|
this.triggerInvocations = new ConcurrentHashMap<>();
|
||||||
@ -264,6 +266,15 @@ public class SceneScriptManager {
|
|||||||
|
|
||||||
this.addGroupSuite(groupInstance, suiteData, entitiesAdded);
|
this.addGroupSuite(groupInstance, suiteData, entitiesAdded);
|
||||||
|
|
||||||
|
// refreshGroup may be called by a trigger.
|
||||||
|
// If that trigger has been refreshed, ensure it does not get
|
||||||
|
// deregistered anyway when the trigger completes its invocation.
|
||||||
|
for (var triggerSet : currentTriggers.values()) {
|
||||||
|
var toSave = new HashSet<SceneTrigger>(triggerSet);
|
||||||
|
toSave.retainAll(ongoingTriggers);
|
||||||
|
toSave.forEach(t -> t.setPreserved(true));
|
||||||
|
}
|
||||||
|
|
||||||
// Refesh variables here
|
// Refesh variables here
|
||||||
group.variables.forEach(
|
group.variables.forEach(
|
||||||
variable -> {
|
variable -> {
|
||||||
@ -925,6 +936,7 @@ public class SceneScriptManager {
|
|||||||
|
|
||||||
private void callTrigger(SceneTrigger trigger, ScriptArgs params) {
|
private void callTrigger(SceneTrigger trigger, ScriptArgs params) {
|
||||||
// the SetGroupVariableValueByGroup in tower need the param to record the first stage time
|
// the SetGroupVariableValueByGroup in tower need the param to record the first stage time
|
||||||
|
ongoingTriggers.add(trigger);
|
||||||
var ret = this.callScriptFunc(trigger.getAction(), trigger.currentGroup, params);
|
var ret = this.callScriptFunc(trigger.getAction(), trigger.currentGroup, params);
|
||||||
var invocationsCounter = triggerInvocations.get(trigger.getName());
|
var invocationsCounter = triggerInvocations.get(trigger.getName());
|
||||||
var invocations = invocationsCounter.incrementAndGet();
|
var invocations = invocationsCounter.incrementAndGet();
|
||||||
@ -956,11 +968,15 @@ public class SceneScriptManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// always deregister on error, otherwise only if the count is reached
|
// always deregister on error, otherwise only if the count is reached
|
||||||
if (ret.isboolean() && !ret.checkboolean()
|
// or the trigger should be preserved after a RefreshGroup call
|
||||||
|
if (trigger.isPreserved()) {
|
||||||
|
trigger.setPreserved(false);
|
||||||
|
} else if (ret.isboolean() && !ret.checkboolean()
|
||||||
|| ret.isint() && ret.checkint() != 0
|
|| ret.isint() && ret.checkint() != 0
|
||||||
|| trigger.getTrigger_count() > 0 && invocations >= trigger.getTrigger_count()) {
|
|| trigger.getTrigger_count() > 0 && invocations >= trigger.getTrigger_count()) {
|
||||||
deregisterTrigger(trigger);
|
deregisterTrigger(trigger);
|
||||||
}
|
}
|
||||||
|
ongoingTriggers.remove(trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params) {
|
private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params) {
|
||||||
@ -1053,18 +1069,7 @@ public class SceneScriptManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate level
|
// Calculate level
|
||||||
int level = monster.level;
|
int level = getScene().getLevelForMonster(monster.config_id, monster.level);
|
||||||
|
|
||||||
if (getScene().getDungeonManager() != null) {
|
|
||||||
level = getScene().getDungeonManager().getLevelForMonster(monster.config_id);
|
|
||||||
} else if (getScene().getWorld().getWorldLevel() > 0) {
|
|
||||||
var worldLevelData =
|
|
||||||
GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel());
|
|
||||||
|
|
||||||
if (worldLevelData != null) {
|
|
||||||
level = worldLevelData.getMonsterLevel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn mob
|
// Spawn mob
|
||||||
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, monster.rot, level);
|
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, monster.rot, level);
|
||||||
@ -1104,6 +1109,19 @@ public class SceneScriptManager {
|
|||||||
return meta.sceneBlockIndex;
|
return meta.sceneBlockIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeMonstersInGroup(SceneGroup group) {
|
||||||
|
var configSet =
|
||||||
|
group.monsters.values().stream().map(m -> m.config_id).collect(Collectors.toSet());
|
||||||
|
var toRemove =
|
||||||
|
getScene().getEntities().values().stream()
|
||||||
|
.filter(e -> e instanceof EntityMonster)
|
||||||
|
.filter(e -> e.getGroupId() == group.id)
|
||||||
|
.filter(e -> configSet.contains(e.getConfigId()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_TYPE_MISS);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
||||||
var configSet = suite.sceneMonsters.stream().map(m -> m.config_id).collect(Collectors.toSet());
|
var configSet = suite.sceneMonsters.stream().map(m -> m.config_id).collect(Collectors.toSet());
|
||||||
var toRemove =
|
var toRemove =
|
||||||
|
@ -201,7 +201,7 @@ public class ScriptLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
||||||
if (towerManager.isInProgress()) {
|
if (towerManager.isInProgress() && towerManager.getCurrentTimeLimit() > 0) {
|
||||||
// Tower scripts call ActiveChallenge twice in mirror stages.
|
// Tower scripts call ActiveChallenge twice in mirror stages.
|
||||||
// The second call provides the time _taken_ in the first stage,
|
// The second call provides the time _taken_ in the first stage,
|
||||||
// not the actual time limit for the challenge.
|
// not the actual time limit for the challenge.
|
||||||
|
@ -17,6 +17,7 @@ public final class SceneTrigger {
|
|||||||
private String tag;
|
private String tag;
|
||||||
|
|
||||||
public transient SceneGroup currentGroup;
|
public transient SceneGroup currentGroup;
|
||||||
|
private boolean preserved;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
|
@ -62,7 +62,11 @@ public final class ScriptMonsterTideService {
|
|||||||
|
|
||||||
public SceneMonster getNextMonster() {
|
public SceneMonster getNextMonster() {
|
||||||
var nextId = this.monsterConfigOrders.poll();
|
var nextId = this.monsterConfigOrders.poll();
|
||||||
if (currentGroup.monsters.containsKey(nextId)) {
|
if (nextId == null) {
|
||||||
|
// AutoMonsterTide has been called with fewer monster config IDs than the total tide count.
|
||||||
|
// Get last config ID from the list, then.
|
||||||
|
return currentGroup.monsters.get(monsterConfigIds.get(monsterConfigIds.size() - 1));
|
||||||
|
} else if (currentGroup.monsters.containsKey(nextId)) {
|
||||||
return currentGroup.monsters.get(nextId);
|
return currentGroup.monsters.get(nextId);
|
||||||
}
|
}
|
||||||
// TODO some monster config_id do not exist in groups, so temporarily set it to the first
|
// TODO some monster config_id do not exist in groups, so temporarily set it to the first
|
||||||
|
@ -218,6 +218,8 @@ public final class HttpServer {
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<img src="https://http.cat/404" />
|
<img src="https://http.cat/404" />
|
||||||
|
<h1>Grasscutter cannot find the route you're trying to access.</h1>
|
||||||
|
<p>Your proxy is active, so if you're trying to download something close the game/stop the proxy.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
""");
|
""");
|
||||||
|
@ -21,7 +21,6 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
|
|||||||
|
|
||||||
// Handle skill notify in other managers.
|
// Handle skill notify in other managers.
|
||||||
player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
|
player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
|
||||||
player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
|
|
||||||
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId);
|
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package emu.grasscutter.server.packet.recv;
|
package emu.grasscutter.server.packet.recv;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.net.packet.Opcodes;
|
import emu.grasscutter.net.packet.Opcodes;
|
||||||
import emu.grasscutter.net.packet.PacketHandler;
|
import emu.grasscutter.net.packet.PacketHandler;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.HomeChangeModuleReqOuterClass;
|
import emu.grasscutter.net.proto.HomeChangeModuleReqOuterClass.HomeChangeModuleReq;
|
||||||
|
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
|
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
import emu.grasscutter.server.packet.send.PacketHomeAvatarTalkFinishInfoNotify;
|
import emu.grasscutter.server.packet.send.PacketHomeAvatarTalkFinishInfoNotify;
|
||||||
@ -16,12 +18,20 @@ public class HandlerHomeChangeModuleReq extends PacketHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
HomeChangeModuleReqOuterClass.HomeChangeModuleReq req =
|
var req = HomeChangeModuleReq.parseFrom(payload);
|
||||||
HomeChangeModuleReqOuterClass.HomeChangeModuleReq.parseFrom(payload);
|
|
||||||
|
|
||||||
var homeWorld = session.getPlayer().getCurHomeWorld();
|
var homeWorld = session.getPlayer().getCurHomeWorld();
|
||||||
if (!homeWorld.getGuests().isEmpty()) {
|
if (!homeWorld.getGuests().isEmpty()) {
|
||||||
session.send(new PacketHomeChangeModuleRsp());
|
session.send(new PacketHomeChangeModuleRsp(Retcode.RET_HOME_HAS_GUEST));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int realmId = 2000 + req.getTargetModuleId();
|
||||||
|
var scene = homeWorld.getSceneById(realmId);
|
||||||
|
|
||||||
|
if (scene == null) {
|
||||||
|
Grasscutter.getLogger().warn("scene == null! Changing module will fail.");
|
||||||
|
session.send(new PacketHomeChangeModuleRsp(Retcode.RET_INVALID_SCENE_ID));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +41,6 @@ public class HandlerHomeChangeModuleReq extends PacketHandler {
|
|||||||
session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer()));
|
session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer()));
|
||||||
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
|
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
|
||||||
|
|
||||||
int realmId = 2000 + req.getTargetModuleId();
|
|
||||||
var scene = homeWorld.getSceneById(realmId);
|
|
||||||
var pos = scene.getScriptManager().getConfig().born_pos;
|
var pos = scene.getScriptManager().getConfig().born_pos;
|
||||||
|
|
||||||
homeWorld.transferPlayerToScene(session.getPlayer(), realmId, TeleportType.WAYPOINT, pos);
|
homeWorld.transferPlayerToScene(session.getPlayer(), realmId, TeleportType.WAYPOINT, pos);
|
||||||
|
@ -34,8 +34,7 @@ public class HandlerHomeSceneJumpReq extends PacketHandler {
|
|||||||
pos = home.getSceneMap().get(realmId).getBornPos();
|
pos = home.getSceneMap().get(realmId).getBornPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
world.transferPlayerToScene(
|
world.transferPlayerToScene(session.getPlayer(), scene.getId(), pos);
|
||||||
session.getPlayer(), req.getIsEnterRoomScene() ? homeScene.getRoomSceneId() : realmId, pos);
|
|
||||||
|
|
||||||
session.send(new PacketHomeSceneJumpRsp(req.getIsEnterRoomScene()));
|
session.send(new PacketHomeSceneJumpRsp(req.getIsEnterRoomScene()));
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,23 @@ package emu.grasscutter.server.packet.send;
|
|||||||
|
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.HomeChangeModuleRspOuterClass;
|
import emu.grasscutter.net.proto.HomeChangeModuleRspOuterClass.HomeChangeModuleRsp;
|
||||||
import emu.grasscutter.net.proto.RetcodeOuterClass;
|
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
|
||||||
|
|
||||||
public class PacketHomeChangeModuleRsp extends BasePacket {
|
public class PacketHomeChangeModuleRsp extends BasePacket {
|
||||||
|
|
||||||
public PacketHomeChangeModuleRsp(int targetModuleId) {
|
public PacketHomeChangeModuleRsp(int targetModuleId) {
|
||||||
super(PacketOpcodes.HomeChangeModuleRsp);
|
super(PacketOpcodes.HomeChangeModuleRsp);
|
||||||
|
|
||||||
HomeChangeModuleRspOuterClass.HomeChangeModuleRsp proto =
|
var proto =
|
||||||
HomeChangeModuleRspOuterClass.HomeChangeModuleRsp.newBuilder()
|
HomeChangeModuleRsp.newBuilder().setRetcode(0).setTargetModuleId(targetModuleId).build();
|
||||||
.setRetcode(0)
|
|
||||||
.setTargetModuleId(targetModuleId)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.setData(proto);
|
this.setData(proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PacketHomeChangeModuleRsp() {
|
public PacketHomeChangeModuleRsp(Retcode retcode) {
|
||||||
super(PacketOpcodes.HomeChangeModuleRsp);
|
super(PacketOpcodes.HomeChangeModuleRsp);
|
||||||
|
|
||||||
this.setData(
|
this.setData(HomeChangeModuleRsp.newBuilder().setRetcode(retcode.getNumber()));
|
||||||
HomeChangeModuleRspOuterClass.HomeChangeModuleRsp.newBuilder()
|
|
||||||
.setRetcode(RetcodeOuterClass.Retcode.RET_HOME_HAS_GUEST_VALUE));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.game.home.HomeBlockItem;
|
import emu.grasscutter.game.home.HomeBlockItem;
|
||||||
import emu.grasscutter.game.home.HomeMarkPointProtoFactory;
|
import emu.grasscutter.game.home.HomeMarkPointProtoFactory;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.packet.*;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.proto.*;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
|
import emu.grasscutter.net.proto.HomeMarkPointNotifyOuterClass.HomeMarkPointNotify;
|
||||||
|
import emu.grasscutter.net.proto.HomeMarkPointSceneDataOuterClass.HomeMarkPointSceneData;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -13,17 +16,24 @@ public class PacketHomeMarkPointNotify extends BasePacket {
|
|||||||
public PacketHomeMarkPointNotify(Player player) {
|
public PacketHomeMarkPointNotify(Player player) {
|
||||||
super(PacketOpcodes.HomeMarkPointNotify);
|
super(PacketOpcodes.HomeMarkPointNotify);
|
||||||
|
|
||||||
var proto = HomeMarkPointNotifyOuterClass.HomeMarkPointNotify.newBuilder();
|
var proto = HomeMarkPointNotify.newBuilder();
|
||||||
var world = player.getCurHomeWorld();
|
var world = player.getCurHomeWorld();
|
||||||
var owner = world.getHost();
|
var owner = world.getHost();
|
||||||
var home = world.getHome();
|
var home = world.getHome();
|
||||||
|
|
||||||
if (owner.getRealmList() == null) {
|
if (owner.getRealmList() == null || owner.getRealmList().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send current home mark points.
|
// send current home mark points.
|
||||||
var moduleId = owner.getCurrentRealmId();
|
var moduleId = owner.getCurrentRealmId();
|
||||||
|
var scene = world.getSceneById(moduleId + 2000);
|
||||||
|
if (scene == null) {
|
||||||
|
Grasscutter.getLogger()
|
||||||
|
.warn(
|
||||||
|
"Current Realm id is invalid! SceneExcelConfigData.json, game resource not loaded correctly or the realm id maybe wrong?!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
var homeScene = home.getHomeSceneItem(moduleId + 2000);
|
var homeScene = home.getHomeSceneItem(moduleId + 2000);
|
||||||
var mainHouse = home.getMainHouseItem(moduleId + 2000);
|
var mainHouse = home.getMainHouseItem(moduleId + 2000);
|
||||||
|
|
||||||
@ -31,12 +41,12 @@ public class PacketHomeMarkPointNotify extends BasePacket {
|
|||||||
.forEach(
|
.forEach(
|
||||||
homeSceneItem -> {
|
homeSceneItem -> {
|
||||||
var markPointData =
|
var markPointData =
|
||||||
HomeMarkPointSceneDataOuterClass.HomeMarkPointSceneData.newBuilder()
|
HomeMarkPointSceneData.newBuilder()
|
||||||
.setModuleId(moduleId)
|
.setModuleId(moduleId)
|
||||||
.setSceneId(homeSceneItem.getSceneId());
|
.setSceneId(homeSceneItem.getSceneId());
|
||||||
|
|
||||||
if (!homeSceneItem.isRoom()) {
|
if (!homeSceneItem.isRoom()) {
|
||||||
var config = world.getSceneById(moduleId + 2000).getScriptManager().getConfig();
|
var config = scene.getScriptManager().getConfig();
|
||||||
markPointData
|
markPointData
|
||||||
.setSafePointPos(
|
.setSafePointPos(
|
||||||
config == null
|
config == null
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.game.entity.EntityMonster;
|
||||||
|
import emu.grasscutter.net.packet.*;
|
||||||
|
import emu.grasscutter.net.proto.MonsterSummonTagNotifyOuterClass.MonsterSummonTagNotify;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class PacketMonsterSummonTagNotify extends BasePacket {
|
||||||
|
|
||||||
|
public PacketMonsterSummonTagNotify(EntityMonster monsterEntity) {
|
||||||
|
super(PacketOpcodes.MonsterSummonTagNotify);
|
||||||
|
|
||||||
|
var proto = MonsterSummonTagNotify.newBuilder().setMonsterEntityId(monsterEntity.getId());
|
||||||
|
monsterEntity.getSummonTagMap().forEach((k, v) -> proto.putSummonTagMap(k, v == null ? 0 : 1));
|
||||||
|
|
||||||
|
this.setData(proto.build());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user