19 Commits

Author SHA1 Message Date
8b83dbf58c Update Korean translation (#1977)
* updated ko-KR.json

Update Korean translation

* Update ko-KR.json
2022-12-11 01:22:34 +10:30
d6fef3252f Formalizing and changing untranslated words (#1973)
Changing non-formal word of "Sebahagian" to "Sebagian", Changing untranslated word, Making stuff slightly more clear
2022-12-10 10:26:46 +10:30
e73984bd41 Update CHS translation (#1965) 2022-12-02 10:25:15 +10:30
553e22ead8 Add more explicit NoSuchFileException message for excels 2022-11-28 21:04:39 +10:30
5878cb6f8d Accidentally left recent banner on test string 2022-11-26 16:50:56 +10:30
100d08ec5d Fix up pity tallies for new BannerTypes
Also fixes Beginner banner using Standard pity.
2022-11-26 02:03:11 +10:30
83b84408a1 Bake banner defaults into BannerType enum, and add every historic banner 2022-11-25 23:25:22 +10:30
da3981089d Add Position JsonAdapter for [x,y,z] format
Also add serializers for existing JsonAdapters
2022-11-25 00:29:26 +10:30
ad502a8568 Finally enforce deprecation of ancient Banners.json fields
Also add costItemId10 column so people know it exists, and removeC6FromPool column because it's a cool setting nobody knows about.
2022-11-24 23:56:13 +10:30
f6c84fdfbf Forgot about a 5star character that was added to the standard pool 2022-11-24 23:17:10 +10:30
1c4d263dd2 Add Data TSJ loading, replace and update Banners 2022-11-24 23:09:55 +10:30
35962542af Fix oversight on EnumTypeAdapterFactory 2022-11-24 01:36:58 +10:30
0b5329514b TSJ and TSV parsing (#1962)
* Deserialization support for tsv files

* Benchmarking

* Apparently moving the setter out of the lambda fixed the setAccessible issue

* Thread it

* Use AllArgsConstructor instead of field reflection

* Clean up AllArgsConstructor TSV deserialization

* Refactor TsvUtils

* Remove AllArgsConstructors from Excels

* Set field accessible

* [WIP] TSJ improvements

* [WIP] More TSV stuff

* [WIP] More TSV stuff

* Working TSV parser (slow)

* Load Excels in TSJ > JSON > TSV priority
2022-11-24 00:18:57 +10:30
46b0c7cf93 Work on French localization (#1958)
* Work on French localization

* Apply suggestions from code review
2022-11-22 20:48:39 +10:30
1e932ce144 added IT redeirect on all ReadME and typo fix (#1954)
* Added it-IT translaton

* added Italian readme

* readme redirect

add "it-IT" redirect on all readme

* added "it-IT" redirect on README.md

* Add files via upload

* added it to jp readme

* fix

* fix whitspasces

(vscode)
2022-11-20 22:45:43 +10:30
b1a9ed0226 Update DeforestationManager.java (#1955)
* Update DeforestationManager.java

* Update src/main/java/emu/grasscutter/game/managers/deforestation/DeforestationManager.java
2022-11-20 18:25:36 +10:30
676ed32a12 Added it-IT translaton (#1946)
* Added it-IT translaton

* added Italian readme
2022-11-14 11:19:35 +10:30
05fe62b49a [FIX] Language es-ES (spanish) (#1941) 2022-11-08 14:54:15 +10:30
b781e560e4 Version 1.4.4-dev [skip actions] 2022-11-05 12:49:35 +00:00
45 changed files with 1779 additions and 403 deletions

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Attention:** We always welcome contributors to the project. Before adding your contribution, please carefully read our [Code of Conduct](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**תשומת לב בבקשה:** אנחנו מקבלים עזרה בפיתוח התוכנה. לפני שאתם תורמים לפרויקט בבקשה תקראו את [תנאי השימוש](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Aantekening:** We verwelkomen altijd bijdragers aan het project. Lees onze [Gedragscode](https://github.com/Grasscutters/Grasscutter/blob/development/README_NL.md#bijdragen-aan-het-project) zorgvuldig door voordat u uw bijdrage toevoegt.

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Atención:** Siempre damos la bienvenida a contribuidores del proyecto. Antes de añadir tu contribución, por favor lee cuidadosamente nuestro [Código de conducta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
@ -67,7 +67,7 @@
2. Establece el proxy de red a `127.0.0.1:8080` o el puerto de proxy que pusiste.
**también puedes usar `start.cmd` para iniciar el servidor y el servicio de proxy automáticamente, pero tienes que configurar el entorno JAVA_HOME**
**También puedes usar `start.cmd` para iniciar el servidor y el servicio de proxy automáticamente, pero tienes que configurar el entorno JAVA_HOME**
### Construcción

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Atensyon:** Ang mga kontributor ay laging welcome sa proyektong ito. Bago mag-bigay ng kontribusyon, basahin muna ng mabuti ang [Code of Conduct](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Attention:** De nouveaux contributeurs sont toujours les bienvenus. Avant d'ajouter votre contribution, veuillez lire le [code de conduite](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Perhatian:** Kami selalu menyambut kontributor untuk proyek ini. Sebelum menambahkan kontribusi Anda, harap baca [Kode Etik](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) kami.
@ -14,7 +14,7 @@
* Daftar teman
* Teleportasi
* Sistem gacha
* Co-op *sebahagian* berfungsi
* Co-op *sebagian* berfungsi
* Memunculkan monster melalui konsol
* Fitur inventaris (menerima item/karakter, meng-upgrade item/karakter, dll)
@ -53,9 +53,9 @@
**Catatan:** Sertifikat CA biasanya disimpan di `%USERPROFILE%\ .mitmproxy`, atau anda dapat download dari `http://mitm.it`
klik dua kali untuk [menginstall](https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate#installing-a-trusted-root-certificate) or ...
klik dua kali untuk [menginstall](https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate#installing-a-trusted-root-certificate) ataupun juga
- Via command line
- melalui command line
```shell
certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer
@ -103,6 +103,6 @@ Anda bisa menemukan output jar di root folder proyek.
# Quick Troubleshooting
* Jika kompilasi tidak berhasil, periksa instalasi JDK Anda (JDK 17 dan validasi variabel bin PATH JDK)
* Klien saya tidak terhubung, tidak login, 4206, dll... - Sebagian besar pengaturan daemon proxy Anda adalah *masalahnya*, jika menggunakan
* Klien saya tidak terhubung, tidak login, 4206, dan lain-lain - Sebagian besar pengaturan daemon proxy Anda adalah *masalahnya*, jika menggunakan
Fiddler pastikan berjalan pada port lain kecuali 8888
* Urutan startup: MongoDB > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Game

108
README_it-IT.md Normal file
View File

@ -0,0 +1,108 @@
![Grasscutter](https://socialify.git.ci/Grasscutters/Grasscutter/image?description=1&forks=1&issues=1&language=1&logo=https%3A%2F%2Fs2.loli.net%2F2022%2F04%2F25%2FxOiJn7lCdcT5Mw1.png&name=1&owner=1&pulls=1&stargazers=1&theme=Light)
<div align="center"><img alt="Documentation" src="https://img.shields.io/badge/Wiki-Grasscutter-blue?style=for-the-badge&link=https://github.com/Grasscutters/Grasscutter/wiki&link=https://github.com/Grasscutters/Grasscutter/wiki"> <img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Grasscutters/Grasscutter?logo=java&style=for-the-badge"> <img alt="GitHub" src="https://img.shields.io/github/license/Grasscutters/Grasscutter?style=for-the-badge"> <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/Grasscutters/Grasscutter?style=for-the-badge"> <img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/Grasscutters/Grasscutter/Build?logo=github&style=for-the-badge"></div>
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](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)
**Attenzione:** Diamo sempre il benvenuto ai contributori del progetto. Prima di aggiungere il tuo contributo, leggi attentamente il nostro [Codice di condotta](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).
## Funzionalità attuali
* Login
* Combattimento
* Lista di amici
* Teletrasporto
* Sistema Gacha
* Cooperativa *parzialmente* funzionale
* Evoca mostri dalla console
* Funzionalità dell'inventario (ricevi oggetti/personaggi, aggiorna oggetti/personaggi, ecc.)
## Guida rapida all'installazione
**Nota:** Per il supporto, unisciti al nostro [Discord](https://discord.gg/T5vZU6UyeG).
### Requisiti
* Java SE - 17 ([link](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html))
**Nota:** se vuoi solo **eseguirlo**, **jre** è sufficiente.
* [MongoDB](https://www.mongodb.com/try/download/community) (consigliato 4.0+)
* Servizio proxy: mitmproxy (mitmdump, consigliato), Fiddler Classic, ecc.
### Esecuzione
**Nota:** Se hai eseguito l'aggiornamento da una versione precedente, rimuovi `config.json` in modo che venga generato di nuovo.
1. Ottieni "grasscutter.jar".
- Scarica da [azioni](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297)
- [Compilalo tu stesso](#Compilazione)
2. Crea una cartella `resources` nella directory in cui si trova grasscutter.jar e sposta lì le cartelle `BinOutput` ed `ExcelBinOutput` *(Vedi il [wiki](https://github.com/Grasscutters/Grasscutter/wiki ) per maggiori dettagli su come ottenerli.)*
3. Eseguire Grasscutter con `java -jar grasscutter.jar`. **Assicurati che il servizio mongodb sia attivo.**
### Connessione client
½. Crea un account usando [il comando corrispondente nella console del server](https://github.com/Grasscutters/Grasscutter/wiki/Commands#targeting).
1. Reindirizza il traffico: (scegli uno)
- mitmdump: `mitmdump -s proxy.py -k`
Autorizza il certificato CA:
**Nota:**Il certificato CA si trova solitamente in `%USERPROFILE%\ .mitmproxy`, oppure puoi scaricarlo da `http://mitm.it`
Fare doppio clic su [installa](https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate#installing-a-trusted-root-certificate) o ...
- Con riga di comando
```shell
certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer
```
- Fiddler Classic: esegui Fiddler Classic, abilita `Decrypt https traffic` nelle opzioni e cambia la porta predefinita in (Strumenti -> Opzioni -> Connessioni) in qualcosa di diverso da `8888`, e carica [questo script](https :/ /github.lunatic.moe/fiddlerscript).
- [File host](https://github.com/Grasscutters/Grasscutter/wiki/Running#traffic-route-map)
2. Impostare il proxy di rete su `127.0.0.1:8080` o la porta proxy impostata.
**Puoi anche usare `start.cmd` per avviare automaticamente il server e il servizio proxy, ma devi impostare l'ambiente JAVA_HOME**
### Compilazione
Grasscutter usa Gradle per gestire le dipendenze e le build.
**Requisiti:**
- [Kit di sviluppo Java SE - 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
- [Git](https://git-scm.com/downloads)
##### Windows
```shell
git clone https://github.com/Grasscutters/Grasscutter.git
cd grasscutter
.\gradlew.bat # Impostazioni dell'ambiente
.\gradlew jar # Compila
```
##### Linux
```bash
git clone https://github.com/Grasscutters/Grasscutter.git
cd grasscutter
chmod +x gradlew
./gradlew jar # Compila
```
Puoi trovare il jar generato nella cartella principale del progetto.
### I comandi sono stati spostati nel [wiki](https://github.com/Grasscutters/Grasscutter/wiki/Commands)!
# Soluzioni agli errori comuni
* Se la compilazione non riesce, controlla l'installazione di JDK (JDK 17 e convalida la variabile JDK bin PATH)
* Il mio client non si connette, non accede, 4206, ecc... - Probabilmente le tue impostazioni proxy sono *il problema*, se usi
Fiddler assicurati di utilizzare una porta diversa da 8888
* Sequenza di avvio: MongoDB > Grasscutter > Servizio proxy (mitmdump, fiddler, ecc.) > Gioco

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**:** 私たちはプロジェクトへの貢献者をいつでも歓迎します。貢献を追加する前に、我々の [行動規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)をよくお読みください。

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**주의 :** 우리는 항상 프로젝트에 기여하는 사람들을 환영합니다. 기여를 하기 전, [행동 지침](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)을 주의 깊게 읽어주세요.

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Uwaga:** Zawsze jesteśmy otwarci na wasz wkład w projekt. Przed zaproponowaniem zmian przeczytaj [zasady postępowania (ENG)](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**Внимание:** Мы всегда рады новому вкладу в проект. Однако, перед тем, как сделать свой вклад, пожалуйста, прочтите наш [кодекс делового поведения](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md).

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**请注意:** 欢迎成为本项目的贡献者。但在提交 PR 之前, 请仔细阅读 [代码规范](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。

View File

@ -3,7 +3,7 @@
<div align="center"><a href="https://discord.gg/T5vZU6UyeG"><img alt="Discord - Grasscutter" src="https://img.shields.io/discord/965284035985305680?label=Discord&logo=discord&style=for-the-badge"></a></div>
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [FR](README_fr-FR.md) | [ES](README_es-ES.md) | [HE](README_HE.md) | [RU](README_ru-RU.md) | [PL](README_pl-PL.md) | [ID](README_id-ID.md) | [KR](README_ko-KR.md) | [FIL/PH](README_fil-PH.md) | [NL](README_NL.md) | [JP](README_ja-JP.md)
[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)
**請注意:** 歡迎成為本專案的貢獻者。在提交 PR 之前, 請仔細閱讀[程式碼規範](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md)。

View File

@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
group = 'xyz.grasscutters'
version = '1.4.3'
version = '1.4.4-dev'
sourceCompatibility = 17
targetCompatibility = 17

View File

@ -5,6 +5,8 @@ import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.TsvUtils;
import lombok.val;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -88,6 +90,17 @@ public class DataLoader {
}
}
public static <T> List<T> loadTableToList(String resourcePath, Class<T> classType) throws IOException {
val path = FileUtils.getDataPathTsjJsonTsv(resourcePath);
Grasscutter.getLogger().info("Loading data table from: "+path);
return switch (FileUtils.getFileExtension(path)) {
case "json" -> JsonUtils.loadToList(path, classType);
case "tsj" -> TsvUtils.loadTsjToListSetField(path, classType);
case "tsv" -> TsvUtils.loadTsvToListSetField(path, classType);
default -> null;
};
}
public static void checkAllFiles() {
try {
List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/");

View File

@ -11,7 +11,10 @@ import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.TsvUtils;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArraySet;
@ -23,6 +26,8 @@ import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@ -32,8 +37,9 @@ import static emu.grasscutter.utils.Language.translate;
public class ResourceLoader {
private static final List<String> loadedResources = new ArrayList<>();
private static final Set<String> loadedResources = new CopyOnWriteArraySet<>();
// Get a list of all resource classes, sorted by loadPriority
public static List<Class<?>> getResourceDefClasses() {
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
Set<?> classes = reflections.getSubTypesOf(GameResource.class);
@ -51,6 +57,25 @@ public class ResourceLoader {
return classList;
}
// Get a list containing sets of all resource classes, sorted by loadPriority
protected static List<Set<Class<?>>> getResourceDefClassesPrioritySets() {
val reflections = new Reflections(ResourceLoader.class.getPackage().getName());
val classes = reflections.getSubTypesOf(GameResource.class);
val priorities = ResourceType.LoadPriority.getInOrder();
Grasscutter.getLogger().debug("Priorities are "+priorities);
val map = new LinkedHashMap<ResourceType.LoadPriority, Set<Class<?>>>(priorities.size());
priorities.forEach(p -> map.put(p, new HashSet<>()));
classes.forEach(c -> {
// val c = (Class<?>) o;
val annotation = c.getAnnotation(ResourceType.class);
if (annotation != null) {
map.get(annotation.loadPriority()).add(c);
}
});
return List.copyOf(map.values());
}
private static boolean loadedAll = false;
public static void loadAll() {
if (loadedAll) return;
@ -86,48 +111,66 @@ public class ResourceLoader {
}
public static void loadResources(boolean doReload) {
for (Class<?> resourceDefinition : getResourceDefClasses()) {
ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);
long startTime = System.nanoTime();
val errors = new ConcurrentLinkedQueue<Pair<String, Exception>>(); // Logger in a parallel stream will deadlock
if (type == null) {
continue;
}
getResourceDefClassesPrioritySets().forEach(classes -> {
classes.stream()
.parallel().unordered()
.forEach(c -> {
val type = c.getAnnotation(ResourceType.class);
if (type == null) return;
@SuppressWarnings("rawtypes")
Int2ObjectMap map = GameData.getMapByResourceDef(resourceDefinition);
val map = GameData.getMapByResourceDef(c);
if (map == null) return;
if (map == null) {
continue;
}
try {
loadFromResource(resourceDefinition, type, map, doReload);
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e.getLocalizedMessage());
}
}
try {
loadFromResource(c, type, map, doReload);
} catch (Exception e) {
errors.add(Pair.of(Arrays.toString(type.name()), e));
}
});
});
errors.forEach(pair -> Grasscutter.getLogger().error("Error loading resource file: " + pair.left(), pair.right()));
long endTime = System.nanoTime();
long ns = (endTime - startTime); //divide by 1000000 to get milliseconds.
Grasscutter.getLogger().debug("Loading resources took "+ns+"ns == "+ns/1000000+"ms");
}
@SuppressWarnings("rawtypes")
protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception {
if (!loadedResources.contains(c.getSimpleName()) || doReload) {
val simpleName = c.getSimpleName();
if (doReload || !loadedResources.contains(simpleName)) {
for (String name : type.name()) {
loadFromResource(c, name, map);
loadFromResource(c, FileUtils.getExcelPath(name), map);
}
loadedResources.add(c.getSimpleName());
Grasscutter.getLogger().debug("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
loadedResources.add(simpleName);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
protected static <T> void loadFromResource(Class<T> c, String fileName, Int2ObjectMap map) throws Exception {
List<T> list = JsonUtils.loadToList(getResourcePath("ExcelBinOutput/" + fileName), c);
for (T o : list) {
protected static <T> void loadFromResource(Class<T> c, Path filename, Int2ObjectMap map) throws Exception {
val results = switch (FileUtils.getFileExtension(filename)) {
case "json" -> JsonUtils.loadToList(filename, c);
case "tsj" -> TsvUtils.loadTsjToListSetField(filename, c);
case "tsv" -> TsvUtils.loadTsvToListSetField(filename, c);
default -> null;
};
if (results == null) return;
results.forEach(o -> {
GameResource res = (GameResource) o;
res.onLoad();
map.put(res.getId(), res);
}
});
}
@SuppressWarnings({"rawtypes", "unchecked"})
protected static <T> void loadFromResource(Class<T> c, String fileName, Int2ObjectMap map) throws Exception {
JsonUtils.loadToList(getResourcePath("ExcelBinOutput/" + fileName), c).forEach(o -> {
GameResource res = (GameResource) o;
res.onLoad();
map.put(res.getId(), res);
});
}
public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints()

View File

@ -2,6 +2,8 @@ package emu.grasscutter.data;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.stream.Stream;
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceType {
@ -28,5 +30,9 @@ public @interface ResourceType {
public int value() {
return value;
}
public static List<LoadPriority> getInOrder() {
return Stream.of(LoadPriority.values()).sorted((x, y) -> y.value() - x.value()).toList();
}
}
}

View File

@ -21,7 +21,7 @@ public class ActivityWatcherData extends GameResource {
@Override
public void onLoad() {
triggerConfig.paramList = triggerConfig.paramList.stream().filter(x -> !x.isBlank()).toList();
triggerConfig.paramList = triggerConfig.paramList.stream().filter(x -> (x != null) && !x.isBlank()).toList();
triggerConfig.watcherTriggerType = WatcherTriggerType.getTypeByName(triggerConfig.triggerType);
}

View File

@ -40,8 +40,11 @@ public class BattlePassMissionData extends GameResource {
@Override
public void onLoad() {
if (this.getTriggerConfig() != null && getTriggerConfig().getParamList()[0].length() > 0) {
this.mainParams = Arrays.stream(getTriggerConfig().getParamList()[0].split("[:;,]")).map(Integer::parseInt).collect(Collectors.toSet());
if (this.getTriggerConfig() != null) {
var params = getTriggerConfig().getParamList()[0];
if ((params != null) && !params.isEmpty()) {
this.mainParams = Arrays.stream(params.split("[:;,]")).map(Integer::parseInt).collect(Collectors.toSet());
}
}
}

View File

@ -11,35 +11,50 @@ import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class GachaBanner {
@Getter private int gachaType;
@Getter private int scheduleId;
@Getter private int gachaType = -1;
@Getter int scheduleId = -1;
@Getter int sortId = -1;
@Getter private String prefabPath;
private String previewPrefabPath;
@Getter private String previewPrefabPath;
@Getter private String titlePath;
private int costItemId = 0;
private int costItemAmount = 1;
private int costItemId10 = 0;
private int costItemAmount10 = 10;
@Getter private int beginTime;
@Getter private int endTime;
@Getter private int sortId;
@Getter private int beginTime = 0;
@Getter private int endTime = 1924992000;
@Getter private int gachaTimesLimit = Integer.MAX_VALUE;
private int[] rateUpItems4 = {};
private int[] rateUpItems5 = {};
@Getter private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
@Getter private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
@Getter private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
@Getter private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041};
@Getter private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
@Getter private boolean removeC6FromPool = false;
@Getter private boolean autoStripRateUpFromFallback = true;
private int[][] weights4 = {{1,510}, {8,510}, {10,10000}};
private int[][] weights5 = {{1,75}, {73,150}, {90,10000}};
private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}};
private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
private int eventChance4 = 50; // Chance to win a featured event item
private int eventChance5 = 50; // Chance to win a featured event item
@Getter private int[] rateUpItems4 = {};
@Getter private int[] rateUpItems5 = {};
// This now handles default values for the fields below
@Getter private BannerType bannerType = BannerType.STANDARD;
// Constants used by the BannerType enum
static final int[][] DEFAULT_WEIGHTS_4 = {{1,510}, {8,510}, {10,10000}};
static final int[][] DEFAULT_WEIGHTS_4_WEAPON = {{1, 600}, {7, 600}, {8, 6600}, {10, 12600}};
static final int[][] DEFAULT_WEIGHTS_5 = {{1,75}, {73,150}, {90,10000}};
static final int[][] DEFAULT_WEIGHTS_5_CHARACTER = {{1,80}, {73,80}, {90,10000}};
static final int[][] DEFAULT_WEIGHTS_5_WEAPON = {{1,100}, {62,100}, {73,7800}, {80,10000}};
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; // Default weapons
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_1 = {1003, 1016, 1042, 1035, 1041, 1069}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; // Default weapons
static final int[] EMPTY_POOL = {}; // Used to remove a type of fallback
// These don't change between banner types (apart from Standard having three extra 4star avatars)
@Getter private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
@Getter private int[] fallbackItems4Pool1 = DEFAULT_FALLBACK_ITEMS_4_POOL_1;
@Getter private int[] fallbackItems4Pool2 = DEFAULT_FALLBACK_ITEMS_4_POOL_2;
// Different banner types have different defaults, see above for default values and the enum for which are used where.
@Getter private int[] fallbackItems5Pool1;
@Getter private int[] fallbackItems5Pool2;
private int[][] weights4;
private int[][] weights5;
private int eventChance4 = -1; // Chance to win a featured event item
private int eventChance5 = -1; // Chance to win a featured event item
//
@Getter private boolean removeC6FromPool = false;
@Getter private boolean autoStripRateUpFromFallback = true; // Ensures that featured items won't "double dip" into the losing pool
private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}}; // Used to ensure that players won't go too many rolls without getting something from pool 1 (avatar) or pool 2 (weapon)
private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
@Getter private int wishMaxProgress = 2;
// Deprecated fields that were tolerated in early May 2022 but have apparently still being circulating in new custom configs
@ -52,11 +67,15 @@ public class GachaBanner {
@Deprecated private int hardPity = -1;
@Deprecated private int minItemType = -1;
@Deprecated private int maxItemType = -1;
@Getter private boolean deprecated = false;
@Getter private boolean disabled = false;
private static void warnDeprecated(String name, String replacement) {
Grasscutter.getLogger().error("Deprecated field found in Banners.json: "+name+" was replaced back in early May 2022, use "+replacement+" instead. If you do not remove this key from your config, it will refuse to load in a future Grasscutter version.");
private void warnDeprecated(String name, String replacement) {
Grasscutter.getLogger().error("Deprecated field found in Banners config: "+name+" was replaced back in early May 2022, use "+replacement+" instead. You MUST remove this field from your config.");
this.deprecated = true;
}
public void onLoad() {
// Handle deprecated configs
if (eventChance != -1)
warnDeprecated("eventChance", "eventChance4 & eventChance5");
if (costItem != 0)
@ -73,30 +92,42 @@ public class GachaBanner {
warnDeprecated("rateUpItems1", "rateUpItems5");
if (rateUpItems2.length > 0)
warnDeprecated("rateUpItems2", "rateUpItems4");
}
public String getPreviewPrefabPath() {
if (this.previewPrefabPath != null && !this.previewPrefabPath.isEmpty())
return this.previewPrefabPath;
return "UI_Tab_" + this.prefabPath;
// Handle default values
if (this.previewPrefabPath != null && this.previewPrefabPath.equals("UI_Tab_" + this.prefabPath))
Grasscutter.getLogger().error("Redundant field found in Banners config: previewPrefabPath does not need to be specified if it is identical to prefabPath prefixed with \"UI_Tab_\".");
if (this.previewPrefabPath == null || this.previewPrefabPath.isEmpty())
this.previewPrefabPath = "UI_Tab_" + this.prefabPath;
if (this.gachaType < 0)
this.gachaType = this.bannerType.gachaType;
if (this.costItemId == 0)
this.costItemId = this.bannerType.costItemId;
if (this.costItemId10 == 0)
this.costItemId10 = this.costItemId;
if (this.weights4 == null)
this.weights4 = this.bannerType.weights4;
if (this.weights5 == null)
this.weights5 = this.bannerType.weights5;
if (this.eventChance4 < 0)
this.eventChance4 = this.bannerType.eventChance4;
if (this.eventChance5 < 0)
this.eventChance5 = this.bannerType.eventChance5;
if (this.fallbackItems5Pool1 == null)
this.fallbackItems5Pool1 = this.bannerType.fallbackItems5Pool1;
if (this.fallbackItems5Pool2 == null)
this.fallbackItems5Pool2 = this.bannerType.fallbackItems5Pool2;
}
public ItemParamData getCost(int numRolls) {
return switch (numRolls) {
case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10);
default -> new ItemParamData(getCostItem(), costItemAmount * numRolls);
case 10 -> new ItemParamData(costItemId10, costItemAmount10);
default -> new ItemParamData(costItemId, costItemAmount * numRolls);
};
}
@Deprecated
public int getCostItem() {
return (costItem > 0) ? costItem : costItemId;
}
public int[] getRateUpItems4() {
return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4;
}
public int[] getRateUpItems5() {
return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5;
return costItemId;
}
public boolean hasEpitomized() {
@ -120,7 +151,7 @@ public class GachaBanner {
public int getEventChance(int rarity) {
return switch (rarity) {
case 4 -> eventChance4;
default -> (eventChance > -1) ? eventChance : eventChance5;
default -> eventChance5;
};
}
@ -138,8 +169,6 @@ public class GachaBanner {
+ "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;
// Grasscutter.getLogger().info("record = " + record);
ItemParamData costItem1 = this.getCost(1);
ItemParamData costItem10 = this.getCost(10);
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
int leftGachaTimes = switch (gachaTimesLimit) {
case Integer.MAX_VALUE -> Integer.MAX_VALUE;
@ -150,10 +179,10 @@ public class GachaBanner {
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(costItem1.getId())
.setCostItemNum(costItem1.getCount())
.setTenCostItemId(costItem10.getId())
.setTenCostItemNum(costItem10.getCount())
.setCostItemId(this.costItemId)
.setCostItemNum(this.costItemAmount)
.setTenCostItemId(this.costItemId10)
.setTenCostItemNum(this.costItemAmount10)
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(details)
@ -202,6 +231,31 @@ public class GachaBanner {
}
public enum BannerType {
STANDARD, EVENT, WEAPON;
STANDARD(200, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
BEGINNER(100, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
EVENT(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2), // Legacy value for CHARACTER
CHARACTER(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
CHARACTER2(400, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
WEAPON(302, 223, DEFAULT_WEIGHTS_4_WEAPON, DEFAULT_WEIGHTS_5_WEAPON, 75, 75, EMPTY_POOL, DEFAULT_FALLBACK_ITEMS_5_POOL_2);
public final int gachaType;
public final int costItemId;
public final int[][] weights4;
public final int[][] weights5;
public final int eventChance4;
public final int eventChance5;
public final int[] fallbackItems5Pool1;
public final int[] fallbackItems5Pool2;
BannerType(int gachaType, int costItemId, int[][] weights4, int[][] weights5, int eventChance4, int eventChance5, int[] fallbackItems5Pool1, int[] fallbackItems5Pool2) {
this.gachaType = gachaType;
this.costItemId = costItemId;
this.weights4 = weights4;
this.weights5 = weights5;
this.eventChance4 = eventChance4;
this.eventChance5 = eventChance5;
this.fallbackItems5Pool1 = fallbackItems5Pool1;
this.fallbackItems5Pool2 = fallbackItems5Pool2;
}
}
}

View File

@ -45,8 +45,6 @@ public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
public GachaSystem(GameServer server) {
super(server);
@ -69,12 +67,24 @@ public class GachaSystem extends BaseGameSystem {
public synchronized void load() {
getGachaBanners().clear();
int autoScheduleId = 1000;
int autoSortId = 9000;
try {
List<GachaBanner> banners = DataLoader.loadList("Banners.json", GachaBanner.class);
List<GachaBanner> banners = DataLoader.loadTableToList("Banners", GachaBanner.class);
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
banner.onLoad();
getGachaBanners().put(banner.getScheduleId(), banner);
if (banner.isDeprecated()) {
Grasscutter.getLogger().error("A Banner has not been loaded because it contains one or more deprecated fields. Remove the fields mentioned above and reload.");
} else if (banner.isDisabled()) {
Grasscutter.getLogger().debug("A Banner has not been loaded because it is disabled.");
} else {
if (banner.scheduleId < 0)
banner.scheduleId = autoScheduleId++;
if (banner.sortId < 0)
banner.sortId = autoSortId--;
getGachaBanners().put(banner.scheduleId, banner);
}
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
@ -156,7 +166,7 @@ public class GachaSystem extends BaseGameSystem {
private synchronized int doFallbackRarePull(int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
return getRandom((rarity==5)? GachaBanner.DEFAULT_FALLBACK_ITEMS_5_POOL_2 : GachaBanner.DEFAULT_FALLBACK_ITEMS_4_POOL_2);
} else {
return getRandom(fallback2);
}

View File

@ -4,37 +4,43 @@ import dev.morphia.annotations.Entity;
@Entity
public class PlayerGachaInfo {
private PlayerGachaBannerInfo standardBanner;
private PlayerGachaBannerInfo eventCharacterBanner;
private PlayerGachaBannerInfo eventWeaponBanner;
private PlayerGachaBannerInfo standardBanner;
private PlayerGachaBannerInfo beginnerBanner;
private PlayerGachaBannerInfo eventCharacterBanner;
private PlayerGachaBannerInfo eventWeaponBanner;
public PlayerGachaInfo() {
this.standardBanner = new PlayerGachaBannerInfo();
this.eventCharacterBanner = new PlayerGachaBannerInfo();
this.eventWeaponBanner = new PlayerGachaBannerInfo();
}
public PlayerGachaInfo() {
this.standardBanner = new PlayerGachaBannerInfo();
this.eventCharacterBanner = new PlayerGachaBannerInfo();
this.eventWeaponBanner = new PlayerGachaBannerInfo();
}
public PlayerGachaBannerInfo getStandardBanner() {
return standardBanner;
}
public PlayerGachaBannerInfo getStandardBanner() {
if (this.standardBanner == null) this.standardBanner = new PlayerGachaBannerInfo();
return this.standardBanner;
}
public PlayerGachaBannerInfo getEventCharacterBanner() {
return eventCharacterBanner;
}
public PlayerGachaBannerInfo getBeginnerBanner() {
if (this.beginnerBanner == null) this.beginnerBanner = new PlayerGachaBannerInfo();
return this.beginnerBanner;
}
public PlayerGachaBannerInfo getEventWeaponBanner() {
return eventWeaponBanner;
}
public PlayerGachaBannerInfo getEventCharacterBanner() {
if (this.eventCharacterBanner == null) this.eventCharacterBanner = new PlayerGachaBannerInfo();
return this.eventCharacterBanner;
}
public PlayerGachaBannerInfo getBannerInfo(GachaBanner banner) {
switch (banner.getBannerType()) {
case EVENT:
return this.eventCharacterBanner;
case WEAPON:
return this.eventWeaponBanner;
case STANDARD:
default:
return this.standardBanner;
}
}
public PlayerGachaBannerInfo getEventWeaponBanner() {
if (this.eventWeaponBanner == null) this.eventWeaponBanner = new PlayerGachaBannerInfo();
return this.eventWeaponBanner;
}
public PlayerGachaBannerInfo getBannerInfo(GachaBanner banner) {
return switch (banner.getBannerType()) {
case STANDARD -> this.getStandardBanner();
case BEGINNER -> this.getBeginnerBanner();
case EVENT, CHARACTER, CHARACTER2 -> this.getEventCharacterBanner();
case WEAPON -> this.getEventWeaponBanner();
};
}
}

View File

@ -36,6 +36,11 @@ public class DeforestationManager extends BasePlayerManager {
ColliderTypeToWoodItemID.put(10,101310);
ColliderTypeToWoodItemID.put(11,101311);
ColliderTypeToWoodItemID.put(12,101312);
ColliderTypeToWoodItemID.put(13,101313);
ColliderTypeToWoodItemID.put(14,101314);
ColliderTypeToWoodItemID.put(15,101315);
ColliderTypeToWoodItemID.put(16,101316);
ColliderTypeToWoodItemID.put(17,101317);
}
public DeforestationManager(Player player) {

View File

@ -1,19 +1,18 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
import lombok.Getter;
public class ItemUseAddExp extends ItemUseAction {
@Getter private int exp = 0;
public class ItemUseAddExp extends ItemUseInt {
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_EXP;
}
public ItemUseAddExp(String[] useParam) {
try {
this.exp = Integer.parseInt(useParam[0]);
} catch (NumberFormatException ignored) {}
super(useParam);
}
public int getExp() {
return this.i;
}
}

View File

@ -14,7 +14,7 @@ public class ItemUseAddItem extends ItemUseInt {
super(useParam);
try {
this.count = Integer.parseInt(useParam[1]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
@Override

View File

@ -1,19 +1,14 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
import lombok.Getter;
public class ItemUseAddReliquaryExp extends ItemUseAction {
@Getter private int exp = 0;
public class ItemUseAddReliquaryExp extends ItemUseAddExp {
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP;
}
public ItemUseAddReliquaryExp(String[] useParam) {
try {
this.exp = Integer.parseInt(useParam[0]);
} catch (NumberFormatException ignored) {}
super(useParam);
}
}

View File

@ -14,7 +14,7 @@ public class ItemUseAddServerBuff extends ItemUseInt {
super(useParam);
try {
this.duration = Integer.parseInt(useParam[1]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
@Override

View File

@ -1,19 +1,14 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
import lombok.Getter;
public class ItemUseAddWeaponExp extends ItemUseAction {
@Getter private int exp = 0;
public class ItemUseAddWeaponExp extends ItemUseAddExp {
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_WEAPON_EXP;
}
public ItemUseAddWeaponExp(String[] useParam) {
try {
this.exp = Integer.parseInt(useParam[0]);
} catch (NumberFormatException ignored) {}
super(useParam);
}
}

View File

@ -15,10 +15,10 @@ public class ItemUseCombineItem extends ItemUseInt {
super(useParam);
try {
this.resultId = Integer.parseInt(useParam[1]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
try {
this.resultCount = Integer.parseInt(useParam[2]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
@Override

View File

@ -19,10 +19,10 @@ public class ItemUseGainAvatar extends ItemUseInt {
super(useParam);
try {
this.level = Integer.parseInt(useParam[1]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
try {
this.constellation = Integer.parseInt(useParam[2]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
@Override

View File

@ -8,6 +8,6 @@ public abstract class ItemUseInt extends ItemUseAction {
public ItemUseInt(String[] useParam) {
try {
this.i = Integer.parseInt(useParam[0]);
} catch (NumberFormatException ignored) {}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
}

View File

@ -1,6 +1,7 @@
package emu.grasscutter.utils;
import emu.grasscutter.Grasscutter;
import lombok.val;
import java.io.File;
import java.io.IOException;
@ -87,6 +88,22 @@ public final class FileUtils {
: Path.of(scripts);
};
private static final String[] TSJ_JSON_TSV = {"tsj", "json", "tsv"};
private static final Path[] DATA_PATHS = {DATA_USER_PATH, DATA_DEFAULT_PATH};
public static Path getDataPathTsjJsonTsv(String filename) {
return getDataPathTsjJsonTsv(filename, true);
}
public static Path getDataPathTsjJsonTsv(String filename, boolean fallback) {
val name = getFilenameWithoutExtension(filename);
for (val data_path : DATA_PATHS) {
for (val ext : TSJ_JSON_TSV) {
val path = data_path.resolve(name + "." + ext);
if (Files.exists(path)) return path;
}
}
return fallback ? DATA_USER_PATH.resolve(name + ".tsj") : null; // Maybe they want to write to a new file
}
public static Path getDataPath(String path) {
Path userPath = DATA_USER_PATH.resolve(path);
if (Files.exists(userPath)) return userPath;
@ -111,6 +128,22 @@ public final class FileUtils {
return RESOURCES_PATH.resolve(path);
}
public static Path getExcelPath(String filename) {
return getTsjJsonTsv(RESOURCES_PATH.resolve("ExcelBinOutput"), filename);
}
// Gets path of a resource.
// If multiple formats of it exist, priority is TSJ > JSON > TSV
// If none exist, return the TSJ path, in case it wants to create a file
public static Path getTsjJsonTsv(Path root, String filename) {
val name = getFilenameWithoutExtension(filename);
for (val ext : TSJ_JSON_TSV) {
val path = root.resolve(name + "." + ext);
if (Files.exists(path)) return path;
}
return root.resolve(name + ".tsj");
}
public static Path getScriptPath(String path) {
return SCRIPTS_PATH.resolve(path);
}
@ -167,14 +200,19 @@ public final class FileUtils {
}
}
@Deprecated // No current uses of this anyway
public static String getFilenameWithoutPath(String fileName) {
int i = fileName.lastIndexOf(".");
if (i > 0) {
return fileName.substring(0, i);
} else {
return fileName;
}
@Deprecated // Misnamed legacy function
public static String getFilenameWithoutPath(String filename) {
return getFilenameWithoutExtension(filename);
}
public static String getFilenameWithoutExtension(String filename) {
int i = filename.lastIndexOf(".");
return (i < 0) ? filename : filename.substring(0, i);
}
public static String getFileExtension(Path path) {
val filename = path.toString();
int i = filename.lastIndexOf(".");
return (i < 0) ? "" : filename.substring(i+1);
}
public static List<Path> getPathsFromResource(String folder) throws URISyntaxException {

View File

@ -10,9 +10,11 @@ import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import emu.grasscutter.data.common.DynamicFloat;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.val;
@ -65,43 +67,85 @@ public class JsonAdapters {
}
@Override
public void write(JsonWriter writer, IntList i) {};
public void write(JsonWriter writer, IntList l) throws IOException {
writer.beginArray();
for (val i : l) // .forEach() doesn't appreciate exceptions
writer.value(i);
writer.endArray();
};
}
static class PositionAdapter extends TypeAdapter<Position> {
@Override
public Position read(JsonReader reader) throws IOException {
switch (reader.peek()) {
case BEGIN_ARRAY: // "pos": [x,y,z]
reader.beginArray();
val array = new FloatArrayList(3);
while (reader.hasNext())
array.add(reader.nextInt());
reader.endArray();
return new Position(array);
case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z}
float x = 0f;
float y = 0f;
float z = 0f;
reader.beginObject();
for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
val name = reader.nextName();
switch (name) {
case "x", "X", "_x" -> x = (float) reader.nextDouble();
case "y", "Y", "_y" -> y = (float) reader.nextDouble();
case "z", "Z", "_z" -> z = (float) reader.nextDouble();
default -> throw new IOException("Invalid field in Position definition - " + name);
}
}
reader.endObject();
return new Position(x, y, z);
default:
throw new IOException("Invalid Position definition - " + reader.peek().name());
}
}
@Override
public void write(JsonWriter writer, Position i) throws IOException {
writer.beginArray();
writer.value(i.getX());
writer.value(i.getY());
writer.value(i.getZ());
writer.endArray();
};
}
static class EnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (!rawType.isEnum()) return null;
Field id = null;
// System.out.println("Looking for enum value field");
for (Field f : rawType.getDeclaredFields()) {
id = switch (f.getName()) {
case "value", "id" -> f;
default -> null;
};
if (id != null) break;
}
if (id == null) {
// System.out.println("Not found");
return null;
}
// System.out.println("Enum value field found - " + id.getName());
Class<T> enumClass = (Class<T>) type.getRawType();
if (!enumClass.isEnum()) return null;
// Make mappings of (string) names to enum constants
val map = new HashMap<String, T>();
boolean acc = id.isAccessible();
id.setAccessible(true);
try {
for (T constant : rawType.getEnumConstants()) {
map.put(constant.toString(), constant);
map.put(String.valueOf(id.getInt(constant)), constant);
val enumConstants = enumClass.getEnumConstants();
for (val constant : enumConstants)
map.put(constant.toString(), constant);
// If the enum also has a numeric value, map those to the constants too
// System.out.println("Looking for enum value field");
for (Field f : enumClass.getDeclaredFields()) {
if (switch (f.getName()) {case "value", "id" -> true; default -> false;}) {
// System.out.println("Enum value field found - " + f.getName());
boolean acc = f.isAccessible();
f.setAccessible(true);
try {
for (val constant : enumConstants)
map.put(String.valueOf(f.getInt(constant)), constant);
} catch (IllegalAccessException e) {
// System.out.println("Failed to access enum id field.");
}
f.setAccessible(acc);
break;
}
} catch (IllegalAccessException e) {
// System.out.println("Failed to access enum id field.");
return null;
}
id.setAccessible(acc);
return new TypeAdapter<T>() {
public T read(JsonReader reader) throws IOException {
@ -114,7 +158,9 @@ public class JsonAdapters {
throw new IOException("Invalid Enum definition - " + reader.peek().name());
}
}
public void write(JsonWriter writer, T value) {}
public void write(JsonWriter writer, T value) throws IOException {
writer.value(value.toString());
}
};
}
}

View File

@ -4,6 +4,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@ -18,6 +19,7 @@ import com.google.gson.reflect.TypeToken;
import emu.grasscutter.data.common.DynamicFloat;
import emu.grasscutter.utils.JsonAdapters.*;
import it.unimi.dsi.fastutil.ints.IntList;
public final class JsonUtils {
@ -25,6 +27,7 @@ public final class JsonUtils {
.setPrettyPrinting()
.registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter())
.registerTypeAdapter(IntList.class, new IntListAdapter())
.registerTypeAdapter(Position.class, new PositionAdapter())
.registerTypeAdapterFactory(new EnumTypeAdapterFactory())
.create();
@ -102,4 +105,12 @@ public final class JsonUtils {
return null;
}
}
public static <T> T decode(String jsonData, Type type) {
try {
return gson.fromJson(jsonData, type);
} catch (Exception ignored) {
return null;
}
}
}

View File

@ -1,6 +1,7 @@
package emu.grasscutter.utils;
import java.io.Serializable;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import com.github.davidmoten.rtreemulti.geometry.Point;
@ -22,9 +23,7 @@ public class Position implements Serializable {
@SerializedName(value="z", alternate={"_z", "Z"})
@Getter @Setter private float z;
public Position() {
}
public Position() {}
public Position(float x, float y) {
set(x, y);
@ -34,6 +33,20 @@ public class Position implements Serializable {
set(x, y, z);
}
public Position(List<Float> xyz) {
switch (xyz.size()) {
default: // Might want to error on excess elements, but maybe we want to extend to 3+3 representation later.
case 3:
this.z = xyz.get(2); // Fall-through
case 2:
this.y = xyz.get(1); // Fall-through
case 1:
this.y = xyz.get(0); // pointless fall-through
case 0:
break;
}
}
public Position(String p) {
String[] split = p.split(",");
if (split.length >= 2) {

View File

@ -0,0 +1,600 @@
package emu.grasscutter.utils;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import lombok.val;
import static emu.grasscutter.utils.Utils.nonRegexSplit;
// Throughout this file, commented System.out.println debug log calls are left in.
// This is because the default logger will deadlock when operating on parallel streams.
public class TsvUtils {
private static final Map<Type, Object> defaultValues = Map.ofEntries(
// Map.entry(String.class, null), // builder hates null values
Map.entry(Integer.class, 0),
Map.entry(int.class, 0),
Map.entry(Long.class, 0L),
Map.entry(long.class, 0L),
Map.entry(Float.class, 0f),
Map.entry(float.class, 0f),
Map.entry(Double.class, 0d),
Map.entry(double.class, 0d),
Map.entry(Boolean.class, false),
Map.entry(boolean.class, false)
);
private static final Set<Type> primitiveTypes = Set.of(String.class, Integer.class, int.class, Long.class, long.class, Float.class, float.class, Double.class, double.class, Boolean.class, boolean.class);
private static final Function<String, Object> parseString = value -> value;
private static final Function<String, Object> parseInt = value -> (int) Double.parseDouble(value); //Integer::parseInt;
private static final Function<String, Object> parseLong = value -> (long) Double.parseDouble(value); //Long::parseLong;
private static Map<Type, Function<String, Object>> primitiveTypeParsers = Map.ofEntries(
Map.entry(String.class, parseString),
Map.entry(Integer.class, parseInt),
Map.entry(int.class, parseInt),
Map.entry(Long.class, parseLong),
Map.entry(long.class, parseLong),
Map.entry(Float.class, Float::parseFloat),
Map.entry(float.class, Float::parseFloat),
Map.entry(Double.class, Double::parseDouble),
Map.entry(double.class, Double::parseDouble),
Map.entry(Boolean.class, Boolean::parseBoolean),
Map.entry(boolean.class, Boolean::parseBoolean)
);
private static final Map<Type, Function<String, Object>> typeParsers = new HashMap<>(primitiveTypeParsers);
@SuppressWarnings("unchecked")
private static <T> T parsePrimitive(Class<T> type, String string) {
if (string == null || string.isEmpty()) return (T) defaultValues.get(type);
return (T) primitiveTypeParsers.get(type).apply(string);
}
// This is more expensive than parsing as the correct types, but it is more tolerant of mismatched data like ints with .0
private static double parseNumber(String string) {
if (string == null || string.isEmpty()) return 0d;
return Double.parseDouble(string);
}
@SuppressWarnings("unchecked")
private static <T> T parseEnum(Class<T> enumType, String string) {
if (string == null || string.isEmpty()) return null;
return (T) getEnumTypeParser(enumType).apply(string);
}
// This is idiotic. I hate it. I'll have to look into how Gson beats the JVM into submission over classes where reflection magically fails to find the NoArgsConstructor later.
public static <T> T newObj(Class<T> objClass) {
try {
return objClass.getDeclaredConstructor().newInstance();
} catch (Exception ignored) {
return JsonUtils.decode("{}", objClass);
}
}
private static final Map<Class<?>, Function<String, Object>> enumTypeParsers = new HashMap<>();
@SuppressWarnings("deprecated") // Field::isAccessible is deprecated because it doesn't do what people think it does. It does what we want it to, however.
private static Function<String, Object> makeEnumTypeParser(Class<?> enumClass) {
if (!enumClass.isEnum()) {
// System.out.println("Called makeEnumTypeParser with non-enum enumClass "+enumClass);
return null;
}
// Make mappings of (string) names to enum constants
val map = new HashMap<String, Object>();
val enumConstants = enumClass.getEnumConstants();
for (val constant : enumConstants)
map.put(constant.toString(), constant);
// If the enum also has a numeric value, map those to the constants too
// System.out.println("Looking for enum value field");
for (Field f : enumClass.getDeclaredFields()) {
if (switch (f.getName()) {case "value", "id" -> true; default -> false;}) {
// System.out.println("Enum value field found - " + f.getName());
boolean acc = f.isAccessible();
f.setAccessible(true);
try {
for (val constant : enumConstants)
map.put(String.valueOf(f.getInt(constant)), constant);
} catch (IllegalAccessException e) {
// System.out.println("Failed to access enum id field.");
}
f.setAccessible(acc);
break;
}
}
return map::get;
}
private static synchronized Function<String, Object> getEnumTypeParser(Class<?> enumType) {
if (enumType == null) {
// System.out.println("Called getEnumTypeParser with null enumType");
return null;
}
return enumTypeParsers.computeIfAbsent(enumType, TsvUtils::makeEnumTypeParser);
}
private static synchronized Function<String, Object> getTypeParser(Type type) {
if (type == null) return parseString;
return typeParsers.computeIfAbsent(type, t -> value -> JsonUtils.decode(value, t));
}
private static Type class2Type(Class<?> classType) {
return (Type) classType.getGenericSuperclass();
}
private static Class<?> type2Class(Type type) {
if (type instanceof Class) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type).getRawType();
} else {
return type.getClass(); // Probably incorrect
}
}
// A helper object that contains a Field and the function to parse a String to create the value for the Field.
private static class FieldParser {
public final Field field;
public final Type type;
public final Class<?> classType;
public final Function<String, Object> parser;
FieldParser(Field field) {
this.field = field;
this.type = field.getGenericType(); // returns specialized type info e.g. java.util.List<java.lang.Integer>
this.classType = field.getType();
this.parser = getTypeParser(this.type);
}
public Object parse(String token) {
return this.parser.apply(token);
}
public void parse(Object obj, String token) throws IllegalAccessException {
this.field.set(obj, this.parser.apply(token));
}
}
private static Map<String, FieldParser> makeClassFieldMap(Class<?> classType) {
val fieldMap = new HashMap<String, FieldParser>();
for (Field field : classType.getDeclaredFields()) {
field.setAccessible(true); // Yes, we don't bother setting this back. No, it doesn't matter for this project.
val fieldParser = new FieldParser(field);
val a = field.getDeclaredAnnotation(SerializedName.class);
if (a == null) { // No annotation, use raw field name
fieldMap.put(field.getName(), fieldParser);
} else { // Handle SerializedNames and alternatives
fieldMap.put(a.value(), fieldParser);
for (val alt : a.alternate()) {
fieldMap.put(alt, fieldParser);
}
}
}
return fieldMap;
}
private static Map<Class<?>, Map<String, FieldParser>> cachedClassFieldMaps = new HashMap<>();
private static synchronized Map<String, FieldParser> getClassFieldMap(Class<?> classType) {
return cachedClassFieldMaps.computeIfAbsent(classType, TsvUtils::makeClassFieldMap);
}
private static class StringTree {
public final Map<String, StringTree> children = new TreeMap<>();
public void addPath(String path) {
if (path.isEmpty()) return;
val firstDot = path.indexOf('.');
val fieldPath = (firstDot < 0) ? path : path.substring(0, firstDot);
val remainder = (firstDot < 0) ? "" : path.substring(firstDot+1);
this.children.computeIfAbsent(fieldPath, k -> new StringTree()).addPath(remainder);
}
}
@SuppressWarnings("unchecked")
private static class StringValueTree {
public final SortedMap<String, StringValueTree> children = new TreeMap<>();
public final Int2ObjectSortedMap<StringValueTree> arrayChildren = new Int2ObjectRBTreeMap<>();
public String value;
public StringValueTree(StringTree from) {
from.children.forEach((k,v) -> {
try {
this.arrayChildren.put(Integer.parseInt(k), new StringValueTree(v));
} catch (NumberFormatException e) {
this.children.put(k, new StringValueTree(v));
}
});
}
public void setValue(String path, String value) {
if (path.isEmpty()) {
this.value = value;
return;
}
val firstDot = path.indexOf('.');
val fieldPath = (firstDot < 0) ? path : path.substring(0, firstDot);
val remainder = (firstDot < 0) ? "" : path.substring(firstDot+1);
try {
this.arrayChildren.get(Integer.parseInt(fieldPath)).setValue(remainder, value);
} catch (NumberFormatException e) {
this.children.get(fieldPath).setValue(remainder, value);
}
}
public JsonElement toJson() {
// Determine if this is an object, an array, or a value
if (this.value != null) { //
return new JsonPrimitive(this.value);
}
if (!this.arrayChildren.isEmpty()) {
val arr = new JsonArray(this.arrayChildren.lastIntKey()+1);
arrayChildren.forEach((k,v) -> arr.set(k, v.toJson()));
return arr;
} else if (this.children.isEmpty()) {
return JsonNull.INSTANCE;
} else {
val obj = new JsonObject();
children.forEach((k,v) -> {
val j = v.toJson();
if (j != JsonNull.INSTANCE)
obj.add(k, v.toJson());
});
return obj;
}
}
public <T> T toClass(Class<T> classType, Type type) {
// System.out.println("toClass called with Class: "+classType+" \tType: "+type);
if (type == null)
type = class2Type(classType);
if (primitiveTypeParsers.containsKey(classType)) {
return parsePrimitive(classType, this.value);
} else if (classType.isEnum()) {
return parseEnum(classType, this.value);
} else if (classType.isArray()) {
return this.toArray(classType);
} else if (List.class.isAssignableFrom(classType)) {
// if (type instanceof ParameterizedType)
val elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
return (T) this.toList(type2Class(elementType), elementType);
} else if (Map.class.isAssignableFrom(classType)) {
// System.out.println("Class: "+classType+" \tClassTypeParams: "+Arrays.toString(classType.getTypeParameters())+" \tType: "+type+" \tTypeArguments: "+Arrays.toString(((ParameterizedType) type).getActualTypeArguments()));
// if (type instanceof ParameterizedType)
val keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
val valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
return (T) this.toMap(type2Class(keyType), type2Class(valueType), valueType);
} else {
return this.toObj(classType, type);
}
}
private <T> T toObj(Class<T> objClass, Type objType) {
try {
// val obj = objClass.getDeclaredConstructor().newInstance();
val obj = newObj(objClass);
val fieldMap = getClassFieldMap(objClass);
this.children.forEach((name, tree) -> {
val field = fieldMap.get(name);
if (field == null) return;
try {
if (primitiveTypes.contains(field.type)) {
if ((tree.value != null) && !tree.value.isEmpty())
field.parse(obj, tree.value);
} else {
val value = tree.toClass(field.classType, field.type);
// System.out.println("Setting field "+name+" to "+value);
field.field.set(obj, value);
// field.field.set(obj, tree.toClass(field.classType, field.type));
}
} catch (Exception e) {
// System.out.println("Exception while setting field "+name+" for class "+objClass+" - "+e);
Grasscutter.getLogger().error("Exception while setting field "+name+" ("+field.classType+")"+" for class "+objClass+" - ",e);
}
});
return obj;
} catch (Exception e) {
// System.out.println("Exception while creating object of class "+objClass+" - "+e);
Grasscutter.getLogger().error("Exception while creating object of class "+objClass+" - ",e);
return null;
}
}
public <T> T toArray(Class<T> classType) {
// Primitives don't play so nice with generics, so we handle all of them individually.
val containedClass = classType.getComponentType();
// val arraySize = this.arrayChildren.size(); // Assume dense 0-indexed
val arraySize = this.arrayChildren.lastIntKey()+1; // Could be sparse!
// System.out.println("toArray called with Class: "+classType+" \tContains: "+containedClass+" \tof size: "+arraySize);
if (containedClass == int.class) {
val output = new int[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (int) parseNumber(tree.value));
return (T) output;
} else if (containedClass == long.class) {
val output = new long[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (long) parseNumber(tree.value));
return (T) output;
} else if (containedClass == float.class) {
val output = new float[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (float) parseNumber(tree.value));
return (T) output;
} else if (containedClass == double.class) {
val output = new double[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (double) parseNumber(tree.value));
return (T) output;
} else if (containedClass == byte.class) {
val output = new byte[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (byte) parseNumber(tree.value));
return (T) output;
} else if (containedClass == char.class) {
val output = new char[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (char) parseNumber(tree.value));
return (T) output;
} else if (containedClass == short.class) {
val output = new short[arraySize];
this.arrayChildren.forEach((idx, tree) -> output[idx] = (short) parseNumber(tree.value));
return (T) output;
} else if (containedClass == boolean.class) {
val output = new boolean[arraySize];
this.arrayChildren.forEach((idx, tree) -> {
val value = ((tree.value == null) || tree.value.isEmpty()) ? false : Boolean.parseBoolean(tree.value);
output[idx] = value;
});
return (T) output;
} else {
val output = Array.newInstance(containedClass, arraySize);
this.arrayChildren.forEach((idx, tree) -> ((Object[]) output)[idx] = tree.toClass(containedClass, null));
return (T) output;
}
}
private <E> List<E> toList(Class<E> valueClass, Type valueType) {
val arraySize = this.arrayChildren.lastIntKey()+1; // Could be sparse!
// System.out.println("toList called with valueClass: "+valueClass+" \tvalueType: "+valueType+" \tof size: "+arraySize);
val list = new ArrayList<E>(arraySize);
// Safe sparse version
for (int i = 0; i < arraySize; i++)
list.add(null);
this.arrayChildren.forEach((idx, tree) -> list.set(idx, tree.toClass(valueClass, valueType)));
return list;
}
private <K,V> Map<K,V> toMap(Class<K> keyClass, Class<V> valueClass, Type valueType) {
val map = new HashMap<K,V>();
val keyParser = getTypeParser(keyClass);
this.children.forEach((key, tree) -> {
if ((key != null) && !key.isEmpty())
map.put((K) keyParser.apply(key), tree.toClass(valueClass, valueType));
});
return map;
}
}
// Flat tab-separated value tables.
// Arrays are represented as arrayName.0, arrayName.1, etc. columns.
// Maps/POJOs are represented as objName.fieldOneName, objName.fieldTwoName, etc. columns.
// This is currently about 25x as slow as TSJ and Gson parsers, likely due to the tree spam.
public static <T> List<T> loadTsvToListSetField(Path filename, Class<T> classType) {
try (val fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
// val fieldMap = getClassFieldMap(classType);
// val constructor = classType.getDeclaredConstructor();
val headerNames = nonRegexSplit(fileReader.readLine(), '\t');
val columns = headerNames.size();
// If we just crawled through all fields to expand potential subobjects, we might hit recursive data structure explosions (e.g. if something has a Player object)
// So we'll only crawl through objects referenced by the header columns
val stringTree = new StringTree();
headerNames.forEach(stringTree::addPath);
return fileReader.lines().parallel().map(line -> {
// return fileReader.lines().map(line -> {
// System.out.println("Processing line of "+filename+" - "+line);
val tokens = nonRegexSplit(line, '\t');
val m = Math.min(tokens.size(), columns);
int t = 0;
StringValueTree tree = new StringValueTree(stringTree);
try {
for (t = 0; t < m; t++) {
String token = tokens.get(t);
if (!token.isEmpty()) {
tree.setValue(headerNames.get(t), token);
}
}
// return JsonUtils.decode(tree.toJson(), classType);
return tree.toClass(classType, null);
} catch (Exception e) {
Grasscutter.getLogger().warn("Error deserializing an instance of class "+classType.getCanonicalName());
Grasscutter.getLogger().warn("At token #"+t+" of #"+m);
Grasscutter.getLogger().warn("Header names are: "+headerNames.toString());
Grasscutter.getLogger().warn("Tokens are: "+tokens.toString());
Grasscutter.getLogger().warn("Stacktrace is: ", e);
// System.out.println("Error deserializing an instance of class "+classType.getCanonicalName());
// System.out.println("At token #"+t+" of #"+m);
// System.out.println("Header names are: "+headerNames.toString());
// System.out.println("Tokens are: "+tokens.toString());
// System.out.println("Json is: "+tree.toJson().toString());
// System.out.println("Stacktrace is: "+ e);
return null;
}
}).toList();
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading file '"+filename+"' - Stacktrace is: ", e);
return null;
}
}
// This uses a hybrid format where columns can hold JSON-encoded values.
// I'll term it TSJ (tab-separated JSON) for now, it has convenient properties.
public static <T> List<T> loadTsjToListSetField(Path filename, Class<T> classType) {
try (val fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
val fieldMap = getClassFieldMap(classType);
val constructor = classType.getDeclaredConstructor();
val headerNames = nonRegexSplit(fileReader.readLine(), '\t');
val columns = headerNames.size();
val fieldParsers = headerNames.stream().map(fieldMap::get).toList();
return fileReader.lines().parallel().map(line -> {
val tokens = nonRegexSplit(line, '\t');
val m = Math.min(tokens.size(), columns);
int t = 0;
try {
T obj = constructor.newInstance();
for (t = 0; t < m; t++) {
val fieldParser = fieldParsers.get(t);
if (fieldParser == null) continue;
String token = tokens.get(t);
if (!token.isEmpty()) {
fieldParser.parse(obj, token);
}
}
return obj;
} catch (Exception e) {
Grasscutter.getLogger().warn("Error deserializing an instance of class "+classType.getCanonicalName());
Grasscutter.getLogger().warn("At token #"+t+" of #"+m);
Grasscutter.getLogger().warn("Header names are: "+headerNames.toString());
Grasscutter.getLogger().warn("Tokens are: "+tokens.toString());
Grasscutter.getLogger().warn("Stacktrace is: ", e);
return null;
}
}).toList();
} catch (NoSuchFileException e) {
Grasscutter.getLogger().error("Error loading file '"+filename+"' - File does not exist. You are missing resources. Note that this file may exist in JSON, TSV, or TSJ format, any of which are suitable.");
return null;
} catch (IOException e) {
Grasscutter.getLogger().error("Error loading file '"+filename+"' - Stacktrace is: ", e);
return null;
} catch (NoSuchMethodException e) {
Grasscutter.getLogger().error("Error loading file '"+filename+"' - Class is missing NoArgsConstructor");
return null;
}
}
// -----------------------------------------------------------------
// Everything below here is for the AllArgsConstructor TSJ parser
// -----------------------------------------------------------------
// Sadly, this is a little bit slower than the SetField version.
// I've left it in as an example of an optimization attempt that didn't work out, since the naive reflection version will tempt people to try things like this.
@SuppressWarnings("unchecked")
private static <T> Pair<Constructor<T>, String[]> getAllArgsConstructor(Class<T> classType) {
for (var c : classType.getDeclaredConstructors()) {
val consParameters = (java.beans.ConstructorProperties) c.getAnnotation(java.beans.ConstructorProperties.class);
if (consParameters != null) {
return Pair.of((Constructor<T>) c, consParameters.value());
}
}
return null;
}
public static <T> List<List<T>> loadTsjsToListsConstructor(Class<T> classType, Path... filenames) throws Exception {
val pair = getAllArgsConstructor(classType);
if (pair == null) {
Grasscutter.getLogger().error("No AllArgsContructor found for class: "+classType);
return null;
}
val constructor = pair.left();
val conArgNames = pair.right();
val numArgs = constructor.getParameterCount();
val argMap = new Object2IntArrayMap<String>();
for (int i = 0; i < conArgNames.length; i++) {
argMap.put(conArgNames[i], i);
}
val argTypes = new Type[numArgs]; // constructor.getParameterTypes() returns base types like java.util.List instead of java.util.List<java.lang.Integer>
for (Field field : classType.getDeclaredFields()) {
int index = argMap.getOrDefault(field.getName(), -1);
if (index < 0) continue;
argTypes[index] = field.getGenericType(); // returns specialized type info e.g. java.util.List<java.lang.Integer>
val a = field.getDeclaredAnnotation(SerializedName.class);
if (a != null) { // Handle SerializedNames and alternatives
argMap.put(a.value(), index);
for (val alt : a.alternate()) {
argMap.put(alt, index);
}
}
}
val argParsers = Stream.of(argTypes).map(TsvUtils::getTypeParser).toList();
val defaultArgs = new Object[numArgs];
for (int i = 0; i < numArgs; i++) {
defaultArgs[i] = defaultValues.get(argTypes[i]);
}
return Stream.of(filenames).parallel().map(filename -> {
try (val fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
val headerNames = nonRegexSplit(fileReader.readLine(), '\t');
val columns = headerNames.size();
val argPositions = headerNames.stream().mapToInt(name -> argMap.getOrDefault(name, -1)).toArray();
return fileReader.lines().parallel().map(line -> {
val tokens = nonRegexSplit(line, '\t');
val args = defaultArgs.clone();
val m = Math.min(tokens.size(), columns);
int t = 0;
try {
for (t = 0; t < m; t++) {
val argIndex = argPositions[t];
if (argIndex < 0) continue;
String token = tokens.get(t);
if (!token.isEmpty()) {
args[argIndex] = argParsers.get(argIndex).apply(token);
}
}
return (T) constructor.newInstance(args);
} catch (Exception e) {
Grasscutter.getLogger().warn("Error deserializing an instance of class "+classType.getCanonicalName()+" : "+constructor.getName());
Grasscutter.getLogger().warn("At token #"+t+" of #"+m);
Grasscutter.getLogger().warn("Arg names are: "+Arrays.toString(conArgNames));
Grasscutter.getLogger().warn("Arg types are: "+Arrays.toString(argTypes));
Grasscutter.getLogger().warn("Default Args are: "+Arrays.toString(defaultArgs));
Grasscutter.getLogger().warn("Args are: "+Arrays.toString(args));
Grasscutter.getLogger().warn("Header names are: "+headerNames.toString());
Grasscutter.getLogger().warn("Header types are: "+IntStream.of(argPositions).mapToObj(i -> (i >= 0) ? argTypes[i] : null).toList());
Grasscutter.getLogger().warn("Tokens are: "+tokens.toString());
Grasscutter.getLogger().warn("Stacktrace is: ", e);
return null;
}
}).toList();
} catch (IOException e) {
Grasscutter.getLogger().error("Error loading file '"+filename+"' - Stacktrace is: ", e);
return null;
}
}).toList();
}
}

View File

@ -395,4 +395,22 @@ public final class Utils {
public static <T> T drawRandomListElement(List<T> list) {
return drawRandomListElement(list, null);
}
/***
* Splits a string by a character, into a list
* @param input The string to split
* @param separator The character to use as the split points
* @return A list of all the substrings
*/
public static List<String> nonRegexSplit(String input, int separator) {
var output = new ArrayList<String>();
int start = 0;
for (int next = input.indexOf(separator); next > 0; next = input.indexOf(separator, start)) {
output.add(input.substring(start, next));
start = next + 1;
}
if (start < input.length())
output.add(input.substring(start));
return output;
}
}

View File

@ -1,83 +0,0 @@
[
{
"comment": "Beginner's Banner. Do not change for no reason.",
"gachaType": 100,
"scheduleId": 803,
"bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A016",
"titlePath": "UI_GACHA_SHOW_PANEL_A016_TITLE",
"costItemId": 224,
"costItemAmount10": 8,
"gachaTimesLimit": 20,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9999,
"rateUpItems5": [],
"rateUpItems4": [1034]
},
{
"comment": "Standard",
"gachaType": 200,
"scheduleId": 893,
"bannerType": "STANDARD",
"prefabPath": "GachaShowPanel_A022",
"titlePath": "UI_GACHA_SHOW_PANEL_A022_TITLE",
"costItemId": 224,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 1000,
"fallbackItems4Pool1": [1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064],
"weights5": [[1,75], [73,150], [90,10000]]
},
{
"comment": "Character Event Banner 1",
"gachaType": 301,
"scheduleId": 903,
"bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A103",
"titlePath": "UI_GACHA_SHOW_PANEL_A081_TITLE",
"costItemId": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9998,
"rateUpItems4": [1032, 1020, 1034],
"rateUpItems5": [1073],
"fallbackItems5Pool2": [],
"weights5": [[1,80], [73,80], [90,10000]]
},
{
"comment": "Character Event Banner 2",
"gachaType": 400,
"scheduleId": 923,
"bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A104",
"titlePath": "UI_GACHA_SHOW_PANEL_A049_TITLE",
"costItemId": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9998,
"rateUpItems4": [1032, 1020, 1034],
"rateUpItems5": [1049],
"fallbackItems5Pool2": [],
"weights5": [[1,80], [73,80], [90,10000]]
},
{
"comment": "Weapon Event Banner",
"gachaType": 302,
"scheduleId": 913,
"bannerType": "WEAPON",
"prefabPath": "GachaShowPanel_A105",
"titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE",
"costItemId": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9997,
"rateUpItems4":[15405, 11402, 13407, 14402, 12403],
"rateUpItems5": [14511, 15509],
"fallbackItems5Pool1": [],
"weights4": [[1,600], [7,600], [8,6600], [10,12600]],
"weights5": [[1,100], [62,100], [73,7800], [80,10000]],
"eventChance4": 75,
"eventChance5": 75
}
]

View File

@ -0,0 +1,94 @@
comment bannerType prefabPath titlePath rateUpItems5 rateUpItems4 disabled costItemId scheduleId sortId beginTime endTime weights5 weights4 fallbackItems5Pool2 fallbackItems5Pool1 fallbackItems4Pool1 eventChance4 eventChance5 costItemId10 costItemAmount10 gachaTimesLimit removeC6FromPool
Beginner's Banner. Do not change for no reason. BEGINNER GachaShowPanel_A016 UI_GACHA_SHOW_PANEL_A016_TITLE [1034] 224 803 9999 1924992000 8 20
Standard STANDARD GachaShowPanel_A022 UI_GACHA_SHOW_PANEL_A022_TITLE 224 893 1000 1924992000 [[1, 75], [73, 150], [90, 10000]] [1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072]
Old Standard image STANDARD GachaShowPanel_A017 UI_GACHA_SHOW_PANEL_A017_TITLE true [1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072]
1.0a character CHARACTER GachaShowPanel_A019 UI_GACHA_SHOW_PANEL_A019_TITLE [1022] [1014, 1031, 1023] true 223 1924992000 [[1, 80], [73, 80], [90, 10000]] []
1.0a weapon WEAPON GachaShowPanel_A020 UI_GACHA_SHOW_PANEL_A020_TITLE [11501, 15502] [11402, 12402, 14402, 15402, 13407] true 223 1924992000 [[1, 100], [62, 100], [73, 7800], [80, 10000]] [[1, 600], [7, 600], [8, 6600], [10, 12600]] [] 75 75
1.0b character CHARACTER GachaShowPanel_A018 UI_GACHA_SHOW_PANEL_A018_TITLE [1029] [1025, 1034, 1043] true
1.0b weapon WEAPON GachaShowPanel_A021 UI_GACHA_SHOW_PANEL_A021_TITLE [14502, 12502] [11403, 15403, 12403, 14403, 13401] true
1.1a character CHARACTER GachaShowPanel_A023 UI_GACHA_SHOW_PANEL_A023_TITLE [1033] [1039, 1024, 1027] true
1.1a weapon WEAPON GachaShowPanel_A025 UI_GACHA_SHOW_PANEL_A025_TITLE [14504, 15501] [11402, 12405, 14409, 15405, 13407] true
1.1b character CHARACTER GachaShowPanel_A024 UI_GACHA_SHOW_PANEL_A024_TITLE [1030] [1044, 1020, 1036] true
1.1b weapon WEAPON GachaShowPanel_A026 UI_GACHA_SHOW_PANEL_A026_TITLE [13504, 12504] [11405, 12402, 14401, 15401, 13401] true
1.2a character CHARACTER GachaShowPanel_A027 UI_GACHA_SHOW_PANEL_A027_TITLE [1038] [1031, 1043, 1032] true
1.2a weapon WEAPON GachaShowPanel_A029 UI_GACHA_SHOW_PANEL_A029_TITLE [11504, 14501] [11401, 12401, 13407, 14403, 15402] true
1.2b character CHARACTER GachaShowPanel_A028 UI_GACHA_SHOW_PANEL_A028_TITLE [1037] [1023, 1025, 1034] true
1.2b weapon WEAPON GachaShowPanel_A030 UI_GACHA_SHOW_PANEL_A030_TITLE [12501, 15502] [11403, 12402, 13401, 14409, 15401] true
1.3a character CHARACTER GachaShowPanel_A031 UI_GACHA_SHOW_PANEL_A031_TITLE [1026] [1039, 1024, 1044] true
1.3a weapon WEAPON GachaShowPanel_A034 UI_GACHA_SHOW_PANEL_A034_TITLE [11505, 13505] [11402, 12403, 15405, 14409, 13407] true
1.3b character CHARACTER GachaShowPanel_A032 UI_GACHA_SHOW_PANEL_A032_TITLE [1042] [1027, 1032, 1014] true
1.3b weapon WEAPON GachaShowPanel_A035 UI_GACHA_SHOW_PANEL_A035_TITLE [13501, 12502] [12410, 13406, 11405, 15403, 14402] true
1.3c character CHARACTER GachaShowPanel_A033 UI_GACHA_SHOW_PANEL_A033_TITLE [1046] [1025, 1023, 1036] true
1.4a character CHARACTER GachaShowPanel_A036 UI_GACHA_SHOW_PANEL_A036_TITLE [1022] [1043, 1020, 1034] true
1.4a weapon WEAPON GachaShowPanel_A038 UI_GACHA_SHOW_PANEL_A038_TITLE [15503, 11502] [11410, 14410, 12401, 15401, 13401] true
1.4b character CHARACTER GachaShowPanel_A037 UI_GACHA_SHOW_PANEL_A037_TITLE [1033] [1045, 1014, 1031] true
1.4b weapon WEAPON GachaShowPanel_A039 UI_GACHA_SHOW_PANEL_A039_TITLE [15501, 14502] [15410, 11401, 12403, 14401, 13407] true
1.5a character CHARACTER GachaShowPanel_A040 UI_GACHA_SHOW_PANEL_A040_TITLE [1030] [1048, 1034, 1039] true
1.5a weapon WEAPON GachaShowPanel_A042 UI_GACHA_SHOW_PANEL_A042_TITLE [11504, 14504] [12410, 13406, 11402, 14409, 15403] true
1.5b character CHARACTER GachaShowPanel_A041 UI_GACHA_SHOW_PANEL_A041_TITLE [1051] [1044, 1025, 1024] true
1.5b weapon WEAPON GachaShowPanel_A043 UI_GACHA_SHOW_PANEL_A043_TITLE [12503, 11501] [11403, 12405, 13401, 14403, 15405] true
1.6a character CHARACTER GachaShowPanel_A044 UI_GACHA_SHOW_PANEL_A018_TITLE [1029] [1014, 1043, 1031] true
1.6a weapon WEAPON GachaShowPanel_A046 UI_GACHA_SHOW_PANEL_A021_TITLE [12501, 14502] [15412, 11405, 12402, 13407, 14402] true
1.6b character CHARACTER GachaShowPanel_A045 UI_GACHA_SHOW_PANEL_A045_TITLE [1047] [1045, 1032, 1020] true
1.6b weapon WEAPON GachaShowPanel_A047 UI_GACHA_SHOW_PANEL_A021_TITLE [11503, 14501] [11410, 14410, 15410, 13401, 12401] true
2.0a character CHARACTER GachaShowPanel_A048 UI_GACHA_SHOW_PANEL_A048_TITLE [1002] [1027, 1036, 1048] true
2.0a weapon WEAPON GachaShowPanel_A050 UI_GACHA_SHOW_PANEL_A021_TITLE [11509, 13502] [12403, 13407, 14401, 11401, 15402] true
2.0b character CHARACTER GachaShowPanel_A049 UI_GACHA_SHOW_PANEL_A049_TITLE [1049] [1053, 1039, 1044] true
2.0b weapon WEAPON GachaShowPanel_A051 UI_GACHA_SHOW_PANEL_A021_TITLE [15509, 11502] [11403, 12405, 13401, 14403, 15401] true
2.1a character CHARACTER GachaShowPanel_A052 UI_GACHA_SHOW_PANEL_A052_TITLE [1052] [1056, 1023, 1043] true
2.1a weapon WEAPON GachaShowPanel_A054 UI_GACHA_SHOW_PANEL_A021_TITLE [13509, 12504] [11405, 12402, 13407, 14402, 15403] true
2.1b character CHARACTER GachaShowPanel_A053 UI_GACHA_SHOW_PANEL_A053_TITLE [1054] [1045, 1024, 1025] true
2.1b weapon WEAPON GachaShowPanel_A055 UI_GACHA_SHOW_PANEL_A021_TITLE [14506, 11505] [11402, 12401, 13401, 14401, 15402] true
2.2a character CHARACTER GachaShowPanel_A056 UI_GACHA_SHOW_PANEL_A037_TITLE [1033] [1027, 1036, 1048] true
2.2a weapon WEAPON GachaShowPanel_A058 UI_GACHA_SHOW_PANEL_A021_TITLE [15507, 14504] [12416, 11401, 13407, 14409, 15405] true
2.2b character CHARACTER GachaShowPanel_A057 UI_GACHA_SHOW_PANEL_A033_TITLE [1046] [1050, 1039, 1053] true
2.2b weapon WEAPON GachaShowPanel_A059 UI_GACHA_SHOW_PANEL_A021_TITLE [13501, 15503] [13416, 15416, 11403, 12405, 14402] true
2.3a character CHARACTER GachaShowPanel_A060 UI_GACHA_SHOW_PANEL_A027_TITLE [1038] [1032, 1034, 1045] true
2.3a character2 CHARACTER2 GachaShowPanel_A062 UI_GACHA_SHOW_PANEL_A041_TITLE [1051] [1032, 1034, 1045] true
2.3a weapon WEAPON GachaShowPanel_A063 UI_GACHA_SHOW_PANEL_A021_TITLE [11503, 12503] [14410, 15410, 11405, 12403, 13401] true
2.3b character CHARACTER GachaShowPanel_A061 UI_GACHA_SHOW_PANEL_A061_TITLE [1057] [1055, 1014, 1023] true
2.3b weapon WEAPON GachaShowPanel_A064 UI_GACHA_SHOW_PANEL_A021_TITLE [12510, 15501] [11410, 15412, 12402, 13407, 14403] true
2.4a character CHARACTER GachaShowPanel_A065 UI_GACHA_SHOW_PANEL_A065_TITLE [1063] [1064, 1027, 1036] true
2.4a character2 CHARACTER2 GachaShowPanel_A066 UI_GACHA_SHOW_PANEL_A031_TITLE [1026] [1064, 1027, 1036] true
2.4a weapon WEAPON GachaShowPanel_A067 UI_GACHA_SHOW_PANEL_A021_TITLE [13507, 13505] [13406, 11402, 12401, 14402, 15401] true
2.4b character CHARACTER GachaShowPanel_A068 UI_GACHA_SHOW_PANEL_A040_TITLE [1030] [1025, 1024, 1048] true
2.4b character2 CHARACTER2 GachaShowPanel_A069 UI_GACHA_SHOW_PANEL_A028_TITLE [1037] [1025, 1024, 1048] true
2.4b weapon WEAPON GachaShowPanel_A070 UI_GACHA_SHOW_PANEL_A021_TITLE [13504, 15502] [12410, 11401, 13401, 14401, 15403] true
2.5a character CHARACTER GachaShowPanel_A071 UI_GACHA_SHOW_PANEL_A071_TITLE [1058] [1031, 1039, 1050] true
2.5a weapon WEAPON GachaShowPanel_A072 UI_GACHA_SHOW_PANEL_A021_TITLE [14509, 11505] [13416, 11403, 12405, 14409, 15402] true
2.5b character CHARACTER GachaShowPanel_A073 UI_GACHA_SHOW_PANEL_A052_TITLE [1052] [1032, 1044, 1056] true
2.5b character2 CHARACTER2 GachaShowPanel_A074 UI_GACHA_SHOW_PANEL_A053_TITLE [1054] [1032, 1044, 1056] true
2.5b weapon WEAPON GachaShowPanel_A075 UI_GACHA_SHOW_PANEL_A021_TITLE [13509, 14506] [12416, 15416, 11405, 13407, 14403] true
2.6a character CHARACTER GachaShowPanel_A076 UI_GACHA_SHOW_PANEL_A076_TITLE [1066] [1043, 1023, 1064] true
2.6a character2 CHARACTER2 GachaShowPanel_A077 UI_GACHA_SHOW_PANEL_A036_TITLE [1022] [1043, 1023, 1064] true
2.6a weapon WEAPON GachaShowPanel_A078 UI_GACHA_SHOW_PANEL_A021_TITLE [11510, 15503] [11402, 12403, 13401, 14402, 15405] true
2.6b character CHARACTER GachaShowPanel_A079 UI_GACHA_SHOW_PANEL_A048_TITLE [1002] [1020, 1045, 1053] true
2.6b weapon WEAPON GachaShowPanel_A080 UI_GACHA_SHOW_PANEL_A021_TITLE [11509, 12504] [11401, 12402, 13407, 14401, 15401] true
2.7a character CHARACTER GachaShowPanel_A081 UI_GACHA_SHOW_PANEL_A081_TITLE [1060] [1014, 1048, 1034] true
2.7a character2 CHARACTER2 GachaShowPanel_A082 UI_GACHA_SHOW_PANEL_A031_TITLE [1026] [1014, 1048, 1034] true
2.7a weapon WEAPON GachaShowPanel_A083 UI_GACHA_SHOW_PANEL_A021_TITLE [15508, 13505] [13406, 14409, 12401, 15403, 11403] true
2.7b character CHARACTER GachaShowPanel_A084 UI_GACHA_SHOW_PANEL_A061_TITLE [1057] [1065, 1036, 1055] true
2.7b weapon WEAPON GachaShowPanel_A085 UI_GACHA_SHOW_PANEL_A021_TITLE [12510, 14504] [12410, 11405, 13401, 14403, 15402] true
2.8a character CHARACTER GachaShowPanel_A086 UI_GACHA_SHOW_PANEL_A045_TITLE [1047] [1059, 1027, 1050] true
2.8a character2 CHARACTER2 GachaShowPanel_A087 UI_GACHA_SHOW_PANEL_A018_TITLE [1029] [1059, 1027, 1050] true
2.8a weapon WEAPON GachaShowPanel_A088 UI_GACHA_SHOW_PANEL_A021_TITLE [11503, 14502] [11410, 15412, 12405, 13407, 14402] true
2.8b character CHARACTER GachaShowPanel_A089 UI_GACHA_SHOW_PANEL_A049_TITLE [1049] [1032, 1044, 1064] true
2.8b weapon WEAPON GachaShowPanel_A090 UI_GACHA_SHOW_PANEL_A021_TITLE [15509, 11504] [14410, 15410, 11402, 12403, 13401] true
3.0a character CHARACTER GachaShowPanel_A091 UI_GACHA_SHOW_PANEL_A091_TITLE [1069] [1067, 1039, 1031] true
3.0a character2 CHARACTER2 GachaShowPanel_A092 UI_GACHA_SHOW_PANEL_A040_TITLE [1030] [1067, 1039, 1031] true
3.0a weapon WEAPON GachaShowPanel_A093 UI_GACHA_SHOW_PANEL_A021_TITLE [15511, 13504] [11401, 12402, 13407, 14401, 15402] true
3.0b character CHARACTER GachaShowPanel_A094 UI_GACHA_SHOW_PANEL_A028_TITLE [1037] [1068, 1043, 1025] true
3.0b character2 CHARACTER2 GachaShowPanel_A095 UI_GACHA_SHOW_PANEL_A053_TITLE [1054] [1068, 1043, 1025] true
3.0b weapon WEAPON GachaShowPanel_A096 UI_GACHA_SHOW_PANEL_A021_TITLE [14506, 15502] [11403, 12401, 13401, 14409, 15405] true
3.1a character CHARACTER GachaShowPanel_A097 UI_GACHA_SHOW_PANEL_A097_TITLE [1071] [1072, 1065, 1053] true
3.1a character2 CHARACTER2 GachaShowPanel_A098 UI_GACHA_SHOW_PANEL_A036_TITLE [1022] [1072, 1065, 1053] true
3.1a weapon WEAPON GachaShowPanel_A099 UI_GACHA_SHOW_PANEL_A021_TITLE [13511, 15503] [12415, 11405, 13407, 14403, 15401] true
3.1b character CHARACTER GachaShowPanel_A100 UI_GACHA_SHOW_PANEL_A0100_TITLE [1070] [1014, 1024, 1023] true
3.1b character2 CHARACTER2 GachaShowPanel_A101 UI_GACHA_SHOW_PANEL_A027_TITLE [1038] [1014, 1024, 1023] true
3.1b weapon WEAPON GachaShowPanel_A102 UI_GACHA_SHOW_PANEL_A021_TITLE [11511, 11505] [11418, 14416, 12405, 13401, 15403] true
3.2a character CHARACTER GachaShowPanel_A103 UI_GACHA_SHOW_PANEL_A0103_TITLE [1073] [1020, 1034, 1032] true
3.2a character2 CHARACTER2 GachaShowPanel_A104 UI_GACHA_SHOW_PANEL_A049_TITLE [1049] [1020, 1034, 1032] true
3.2a weapon WEAPON GachaShowPanel_A105 UI_GACHA_SHOW_PANEL_A021_TITLE [14511, 15509] [11402, 12403, 13407, 14402, 15405] true
3.2b character CHARACTER GachaShowPanel_A106 UI_GACHA_SHOW_PANEL_A071_TITLE [1058] [1074, 1050, 1059]
3.2b character2 CHARACTER2 GachaShowPanel_A107 UI_GACHA_SHOW_PANEL_A037_TITLE [1033] [1074, 1050, 1059]
3.2b weapon WEAPON GachaShowPanel_A108 UI_GACHA_SHOW_PANEL_A021_TITLE [14509, 15507] [11401, 12402, 13401, 14401, 15402]

View File

@ -6,7 +6,7 @@
"connect": "Cliente conectado desde %s",
"disconnect": "Cliente desconectado desde %s",
"game_update_error": "Ha ocurrido un error durante la actualización del juego.",
"command_error": "Comando de error:"
"command_error": "Error de comando:"
},
"dispatch": {
"address_bind": "[Dispatch] Servidor de envio iniciado en \u001b[1m\u001b[33m%s:%s\u001b[0m",
@ -19,10 +19,10 @@
"default_password": "[Dispatch] La contraseña por defecto del keystore se cargó correctamente. Por favor, considera establecer la contraseña a 123456 en config.json."
},
"authentication": {
"default_unable_to_verify": "[Authentication] Algo invocó el método verifyUser que no está disponible en el manejador de autentificación por defecto."
"default_unable_to_verify": "[Authentication] Algo llamado método de verifyUser que no está disponible en el controlador de autenticación por defecto."
},
"no_commands_error": "No se soporta el uso de comandos en modo de solo envío.",
"unhandled_request_error": "[Dispatch] Petición potencialmente no manejada %s : %s.",
"no_commands_error": "No se soporta el uso de comandos en dispatch solo envío.",
"unhandled_request_error": "[Dispatch] Posible solicitud %s no gestionada: %s.",
"account": {
"login_attempt": "[Dispatch] El cliente %s está intentando iniciar sesión.",
"login_success": "[Dispatch] El cliente %s inició sesión como %s.",
@ -74,7 +74,7 @@
"permission_error": "No tienes permiso para ejecutar este comando.",
"console_execute_error": "Este comando solo puede ser ejecutado desde la consola.",
"player_execute_error": "Ejecuta este comando desde el juego.",
"command_exist_error": "Ningún comando encontrado.",
"command_exist_error": "Comando no encontrado.",
"no_usage_specified": "No se ha especificado el uso",
"no_description_specified": "No se ha especificado la descripción",
"set_to": "Se ha establecido %s a %s.",
@ -96,7 +96,7 @@
}
},
"execution": {
"usage_prefix": "Uso: ",
"usage_prefix": "Usa: ",
"player_exist_error": "Jugador no encontrado.",
"player_offline_error": "El jugador no está activo.",
"item_player_exist_error": "Objeto o UID inválido.",
@ -106,9 +106,9 @@
"set_target": "Los siguientes comandos irán solo a @%s por defecto.",
"set_target_online": "@%s está activo. Algunos comandos pueden requerir un usuario inactivo.",
"set_target_offline": "@%s está inactivo. Algunos comandos pueden requerir un usuario activo.",
"need_target": "Este comando requiere un UID objetivo. Añade un argumento <@UID> o fija un objetivo persistente con /target @UID.",
"need_target_online": "Este comando requiere un UID objetivo activo, pero el objetivo actual está inactivo. Añade un argumento <@UID> diferente o fija un objetivo persistente con /target @UID.",
"need_target_offline": "Este comando requiere un UID objetivo inactivo, pero el objetivo actual está activo. Añade un argumento <@UID> diferente o fija un objetivo persistente con /target @UID."
"need_target": "Este comando requiere un UID fijado. Añade un argumento <@UID> o fija un objetivo persistente con /target @UID.",
"need_target_online": "Este comando requiere un UID fijado activo, pero el objetivo actual está inactivo. Añade un argumento <@UID> diferente o fija un objetivo persistente con /target @UID.",
"need_target_offline": "Este comando requiere un UID fijado inactivo, pero el objetivo actual está activo. Añade un argumento <@UID> diferente o fija un objetivo persistente con /target @UID."
},
"status": {
"enabled": "Activado",
@ -122,11 +122,11 @@
"create": "Cuenta creada con UID %s.",
"delete": "Cuenta borrada.",
"no_account": "Cuenta no encontrada.",
"description": "Modifica las cuentas de usuario"
"description": "Modifica las cuentas de usuarios"
},
"announce": {
"send_success": "Se envió el anuncio correctamente, puedes revocarlo con /a revoke %s.",
"refresh_success": "Se refrescó el archivo de configuración de anuncio correctamente. [Total %s]",
"refresh_success": "Se actualizó el archivo de configuración de anuncio correctamente. [Total %s]",
"revoke_done": "Intentando revocar el anuncio %s.",
"not_found": "No se pudo encontrar el anuncio %s.",
"description": "Envía un anuncio a todos los jugadores activos, o gestiona anuncios del server"
@ -142,8 +142,8 @@
"description": "Elimina objetos desbloqueados no equipados, incluyendo objetos de rareza dorada, de tu inventario"
},
"coop": {
"success": "Invocado %s al mundo de %s.",
"description": "Fuerza a alguien a ser invocado al mundo de otro. Si no se establece un objetivo, te envía a ti al modo cooperativo."
"success": "%s se ha unido al mundo de %s.",
"description": "Fuerza a alguien a unirse al mundo de otro. Si no se establece un objetivo, te envía a ti al modo cooperativo."
},
"enter_dungeon": {
"changed": "Cambiado a la mazmorra %s.",
@ -152,7 +152,7 @@
"description": "Te introduce en una mazmorra"
},
"give": {
"usage_relic": "Uso: give <artifactID> [mainPropID] [<appendPropID>[,<veces>]]... [lv<nivel 0-20>]",
"usage_relic": "Usa: give <artifactID> [mainPropID] [<appendPropID>[,<veces>]]... [lv<nivel 0-20>]",
"illegal_relic": "Este artifactID pertenece a un rango de la lista negra, puede que no sea el que buscas.",
"given": "Dado %s de %s a %s.",
"given_with_level_and_refinement": "Dado %s con nivel %s, refinamiento %s %s veces a %s.",
@ -177,7 +177,7 @@
"kick": {
"player_kick_player": "El jugador [%s:%s] ha echado al jugador [%s:%s]",
"server_kick_player": "Echando al jugador [%s:%s]...",
"description": "Echa al jugador específico del servidor (WIP)"
"description": "Echa a un jugador en específico del servidor (WIP)"
},
"killall": {
"scene_not_found_in_player_world": "Escenario no encontrado en el mundo del jugador.",
@ -229,10 +229,10 @@
},
"resetShopLimit": {
"success": "Reinicio completado.",
"description": "Reinicia el tiempo de refrescado de la tienda del jugador objetivo"
"description": "Reinicia el tiempo de la tienda del jugador fijado"
},
"sendMail": {
"give_usage": "Uso: give <player> <itemID|itemName> [cantidad] [nivel] [refinamiento]",
"give_usage": "Usa: give <player> <itemID|itemName> [cantidad] [nivel] [refinamiento]",
"user_not_exist": "El usuario con ID '%s' No existe.",
"start_composition": "Empezando la construcción del correo.\nPor favor usa '/sendmail <título>' para continuar.\nPuedes usar '/sendmail stop' en cualquier momento.",
"templates": "Las plantillas de correos se implementarán pronto...",
@ -264,17 +264,17 @@
"fail": "Error al establecer la constelación.",
"failed_success": "Las constelaciones de %s han sido establecidas a %s. Por favor reinicia el escenario para ver los cambios.",
"success": "Las constelaciones de %s han sido establecidas a %s.",
"successall": "🇺🇸Constellations for all characters have been set to %s.",
"description": "Establece el nivel de constelación para tu personaje actual"
"successall": "Las constelaciones de todos los personajes han sido establecidas a %s.",
"description": "Establece el nivel de constelación para tu personaje actual."
},
"setFetterLevel": {
"range_error": "El nivel de amistad debe estar entre 0 y 10.",
"success": "Nivel de amistad establecido a %s.",
"level_error": "Nivel de amistad inválido.",
"description": "Establece tu nivel de amistad para tu personaje actual"
"description": "Establece tu nivel de amistad para tu personaje actual."
},
"setProp": {
"description": "Establece propiedades de la cuenta. Cosas como el modo Dios pueden ser establecidos con este comando, además de cambiar cosas como desbloquear pisos del abusmo o progreso del pase de batalla.\n\tValores para <prop>: godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel\n\t(cont.) Observa PlayerProperty enum para ver otros posibles valores, de la forma PROP_MAX_SPRING_VOLUME -> max_spring_volume"
"description": "Establece propiedades de la cuenta. Cosas como el modo Dios pueden ser establecidos con este comando, además de cambiar cosas como desbloquear pisos del abismo o progreso del pase de batalla.\n\tValores para <prop>: godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel\n\t(cont.) Observa PlayerProperty enum para ver otros posibles valores, de la forma PROP_MAX_SPRING_VOLUME -> max_spring_volume"
},
"setStats": {
"description": "Establece propiedades de combate para tu personaje actual\n\tValores para <estado>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de daño elemental: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Resistencia elemental: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys",
@ -315,10 +315,10 @@
"failed_parse_avatar_id": "Error al usar el ID del avatar: %s",
"avatar_already_in_team": "El avatar ya está en el equipo.",
"avatar_not_found": "Avatar %d no encontrado.",
"description": "Modifica tu equipo a mano."
"description": "Modifica tu equipo manualmente."
},
"teleportAll": {
"success": "Invocados todos los jugadores a tu localización.",
"success": "Invoca a todos los jugadores a tu localización.",
"error": "Solo puedes usar este comando en modo MP.",
"description": "Teletransporta a todos los jugadores en tu mundo a tu localización"
},
@ -337,11 +337,11 @@
"success": "Exitoso.",
"failure": "Error, jugador no encontrado.",
"invalid_time": "No se puede establecer la marca de tiempo.",
"description": "Beta a un jugador"
"description": "Bannea a un jugador"
},
"unlockall": {
"success": "Desfijados todos los estados abiertos para %s.",
"description": "Desfija todos los estados abiertos para un jugador."
"success": "Desbloqueada todas las estadísticas y estados para %s.",
"description": "Desbloquea todas las estadísticas y estados para un jugador."
},
"unban": {
"success": "Exitoso.",
@ -364,23 +364,23 @@
},
"documentation": {
"handbook": {
"title": "🇺🇸GM Handbook",
"title": "GM Handbook",
"title_commands": "Comandos",
"title_avatars": "Avatares",
"title_items": "Objetos",
"title_scenes": "Escenario",
"title_monsters": "Monstruos",
"header_id": "🇺🇸Id",
"header_id": "Id",
"header_command": "Comando",
"header_description": "Descripción",
"header_avatar": "🇺🇸Avatar",
"header_avatar": "Avatar",
"header_item": "Objeto",
"header_scene": "Escenario",
"header_monster": "Monstruo"
},
"index": {
"title": "Documentación",
"handbook": "🇺🇸GM Handbook",
"handbook": "GM Handbook",
"gacha_mapping": "JSON de mapeo del Gacha"
}
},

View File

@ -1,7 +1,7 @@
{
"messages": {
"game": {
"address_bind": "🇺🇸Game Server started at \u001b[1m\u001b[33m%s:%s\u001b[0m",
"address_bind": "Serveur de jeu démarré a l'adresse \u001b[1m\u001b[33m%s:%s\u001b[0m",
"port_bind": "Serveur de jeu démarré sur le port %s",
"connect": "Client connecté depuis %s",
"disconnect": "Client déconnecté depuis %s",
@ -9,7 +9,7 @@
"command_error": "Erreur de commande:"
},
"dispatch": {
"address_bind": "🇺🇸[Dispatch] Dispatch server started at \u001b[1m\u001b[33m%s:%s\u001b[0m",
"address_bind": "[Dispatch] Serveur de répartition \u001b[1m\u001b[33m%s:%s\u001b[0m",
"port_bind": "[Dispatch] Serveur de répartition démarré sur le port %s",
"request": "[Dispatch] Le client %s %s demande : %s",
"keystore": {
@ -248,7 +248,7 @@
"send": "%s %s (niveau %s) ont été ajouté au message.\nContinuez d'ajouter plus d'objets ou utilisez '/sendmail finish' pour envoyer le message.",
"invalid_arguments_please_use": "Arguments invalides.\n Veuillez utiliser '/sendmail %s'",
"title": "<titre>",
"message": "🇺🇸<message>",
"message": "< message >",
"sender": "<expéditeur>",
"arguments": "<itemID|itemName|finish> [quantité] [niveau]",
"error": "ERREUR: Stade de construction invalide : %s. Vérifiez la console pour la pile d'appels.",
@ -259,13 +259,13 @@
"description": "Envoie un message au joueur spécifié en tant que Serveur"
},
"setConst": {
"range_error": "🇺🇸Constellation level must be between 0 and 6.",
"level_error": "🇺🇸Invalid constellation level.",
"fail": "🇺🇸Failed to set constellation.",
"failed_success": "🇺🇸Constellations for %s have been set to %s. Please reload scene to see changes.",
"success": "🇺🇸Constellations for %s have been set to %s.",
"successall": "🇺🇸Constellations for all characters have been set to %s.",
"description": "🇺🇸Sets constellation level for your current active character"
"range_error": "Le niveau de constellation doit être compris entre 1 et 6.",
"level_error": "Niveau de constellation invalide",
"fail": "Impossible de définir le niveau de constellation",
"failed_success": "Les constellations de %s ont été défini à %s. Veuillez recharger la scène pour voir les changements",
"success": "Les constellations de %s ont été défini à %s.",
"successall": "Les constellations de tous vos personnages ont été défini à %s.",
"description": "Définit le niveau de constellation du personnage actif"
},
"setFetterLevel": {
"range_error": "Le niveau d'affinité doit être compris entre 0 et 10.",
@ -278,10 +278,10 @@
},
"setStats": {
"description": "Définit les propriétés de combat de votre personnage actif\n\tValeurs pour <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de dégât élémentaire: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Résistance élémentaire: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys",
"locked_to": "🇺🇸%s locked to %s.",
"locked_for_to": "🇺🇸%s for %s locked to %s.",
"unlocked": "🇺🇸%s unlocked.",
"unlocked_for": "🇺🇸%s for %s unlocked."
"locked_to": "%s verrouillé à %s.",
"locked_for_to": "%s de %s verrouillé à %s.",
"unlocked": "%s déverrouillé.",
"unlocked_for": "%s de %s déverrouillé."
},
"spawn": {
"success": " %s %s sont apparu.",
@ -293,9 +293,9 @@
"description": "Arrête le serveur"
},
"talent": {
"out_of_range": "🇺🇸Invalid talent level. Level should be in range of 1-15.",
"set_id": "🇺🇸Set talent %s - \"%s\" to %s.",
"id_desc": "🇺🇸Talent %s - \"%s\" - \"%s\"",
"out_of_range": "Niveau de talent invalide. Le niveau doit être compris entre 1-15.",
"set_id": "Talent %s - \"%s\" défini à %s.",
"id_desc": "Talent %s : \"%s\" - \"%s\"",
"invalid_skill_id": "ID de talent invalide.",
"invalid_level": "Niveau de talent invalide.",
"normal_attack_id": "ID de l'attaque normale ID %s.",
@ -340,8 +340,8 @@
"description": "Bannis un joueur"
},
"unlockall": {
"success": "🇺🇸Unlocked all open states for %s.",
"description": "🇺🇸Unlocks all open states for a player."
"success": "Toutes les open states ont été débloqués pour %s.",
"description": "Débloque toutes les open states d'un joueur"
},
"unban": {
"success": "Succès.",
@ -358,7 +358,7 @@
},
"records": {
"title": "Historique de voeux",
"date": "🇺🇸Date",
"date": "Date.",
"item": "Objet"
}
},
@ -366,38 +366,38 @@
"handbook": {
"title": "Manuel GM",
"title_commands": "Commandes",
"title_avatars": "🇺🇸Avatars",
"title_avatars": "Avatars.",
"title_items": "Objets",
"title_scenes": "Scènes",
"title_monsters": "Monstres",
"header_id": "🇺🇸Id",
"header_id": "Identifiant",
"header_command": "Commande",
"header_description": "🇺🇸Description",
"header_avatar": "🇺🇸Avatar",
"header_description": "Description.",
"header_avatar": "Avatar.",
"header_item": "Objet",
"header_scene": "Scène",
"header_monster": "Monstre"
},
"index": {
"title": "🇺🇸Documentation",
"title": "Documentation",
"handbook": "Manuel GM",
"gacha_mapping": "🇺🇸Gacha mapping JSON"
"gacha_mapping": "Gacha mapping JSON"
}
},
"plugin": {
"directory_failed": "🇺🇸Failed to create plugins directory: ",
"unable_to_load": "🇺🇸Unable to load plugin.",
"invalid_config": "🇺🇸Plugin %s has an invalid config file.",
"invalid_main_class": "🇺🇸Plugin %s has an invalid main class.",
"missing_config": "🇺🇸Plugin %s lacks a valid config file.",
"failed_to_load_plugin": "🇺🇸Failed to load plugin: %s",
"failed_to_load": "🇺🇸Failed to load a plugin.",
"failed_to_load_dependencies": "🇺🇸Failed to load plugins with dependencies.",
"loading_plugin": "🇺🇸Loading plugin: %s",
"failed_add_id": "🇺🇸Failed to add plugin identifier: %s",
"enabling_plugin": "🇺🇸Enabling plugin: %s",
"enabling_failed": "🇺🇸Failed to enable plugin: %s",
"disabling_plugin": "🇺🇸Disabling plugin: %s",
"disabling_failed": "🇺🇸Failed to disable plugin: %s"
"directory_failed": "Impossible de créer le dossier plugins: ",
"unable_to_load": "Impossible de charger les plugins.",
"invalid_config": "Le plugin %s a un fichier de configuration invalide.",
"invalid_main_class": "Le plugin %s a une classe principale invalide.",
"missing_config": "Le plugin %s manque d'une configuration valide.",
"failed_to_load_plugin": "Impossible de charger le plugin %s",
"failed_to_load": "Impossible de charger un plugin.",
"failed_to_load_dependencies": "Impossible de charger les plugins avec une ou des dépendances.",
"loading_plugin": "Chargement du plugin %s",
"failed_add_id": "Impossible d'ajouter l'identifiant du plugin %s",
"enabling_plugin": "Activation du plugin %s",
"enabling_failed": "Impossible d'activer le plugin %s",
"disabling_plugin": "Désactivation du plugin %s",
"disabling_failed": "Impossible de désactiver le plugin %s"
}
}

View File

@ -0,0 +1,402 @@
{
"messages": {
"game": {
"address_bind": "Server di gioco avviato su \u001B[1m\u001B[33m%s:%s\u001B[0m",
"port_bind": "Server di gioco avviato sulla porta %s",
"connect": "Client connesso da %s",
"disconnect": "Client disconnesso da %s",
"game_update_error": "Si è verificato un errore durante l'aggiornamento del gioco.",
"command_error": "Errore comando:"
},
"dispatch": {
"address_bind": "[Dispatch] Dispatch server avviato su \u001B[1m\u001B[33m%s:%s\u001B[0m",
"port_bind": "[Dispatch] Dispatch server avviato sulla porta %s",
"request": "[Dispatch] Client %s %s richiesta: %s",
"keystore": {
"general_error": "[Dispatch] Errore nel caricamento di keystore!",
"password_error": "[Dispatch] Impossibile caricare il keystore. Provando passwrd di default keystore...",
"no_keystore_error": "[Dispatch] Nessun cert SSL trovato! Ritorno ad un server HTTP.",
"default_password": "[Dispatch] La password di default del keystore è stata caricata con successo. Considera di impostare la password a 123456 in config.json."
},
"authentication": {
"default_unable_to_verify": "[Authentication] [Autenticazione] Qualcosa ha chiamato metodo VerifyUser che non è disponibile nel gestore di autenticazione predefinito."
},
"no_commands_error": "I comandi non sono supportati in modalità solo dispatch.",
"unhandled_request_error": "[Dispatch] Potenziali %s richieste non gestite: %s.",
"account": {
"login_attempt": "[Dispatch] Il client %s sta provando a fare il login.",
"login_success": "[Dispatch] Client %s loggato come %s.",
"login_max_player_limit": "[Dispatch] Client %s non è riuscito ad accedere: Il numero di giocatori online ha raggiunto il limite",
"login_token_attempt": "[Dispatch] Il client %s sta tentando di accedere tramite token.",
"login_token_error": "[Dispatch] Client %s non è riuscito ad accedere tramite token.",
"login_token_success": "[Dispatch] Client %s ha effettuato l'accesso tramite token come %s.",
"login_password_error": "[Dispatch] Client %s non è riuscito ad accedere tramite password.",
"login_password_storage_error": "[Dispatch] Client %s non è riuscito ad accedere tramite password perché non c'è password nel database.",
"combo_token_success": "[Dispatch] Il client %s è riuscito a scambiare il token combinato.",
"combo_token_error": "Il client [Dispatch] %s non è riuscito a scambiare il token combinato.",
"account_login_create_success": "[Dispatch] Client %s non è riuscito ad accedere: Account %s creato.",
"account_login_create_error": "[Dispatch] Client %s non è riuscito ad accedere: Creazione account non riuscita.",
"account_login_exist_error": "[Dispatch] Client %s non è riuscito ad accedere: Account non trovato.",
"account_cache_error": "Errore di informazioni sulla cache dell'account di gioco.",
"session_key_error": "Chiave di sessione errata.",
"username_error": "Nome utente non trovato.",
"username_create_error": "Nome utente non trovato, creazione non riuscita.",
"password_error": "Password non valida",
"password_length_error": "La lunghezza della password deve essere maggiore o uguale a 8",
"password_storage_error": "Non hai una password per il tuo account. Contatta un amministratore.",
"server_max_player_limit": "Il numero di giocatori online ha raggiunto il limite"
},
"router_error": "[Dispatch] Impossibile collegare il router."
},
"status": {
"free_software": "Grasscutter è un software GRATUITO. Se hai pagato per questo, potresti essere stato truffato. Homepage: https://github.com/Grasscutters/Grasscutter",
"starting": "Avvio di Grasscutter...",
"shutdown": "Chiusura in corso...",
"done": "Fatto! Per aiuto, digita \"help\"",
"error": "Si è verificato un errore.",
"welcome": "Benvenuto in Grasscutter!",
"run_mode_error": "Modalità di esecuzione del server non valida: %s.",
"run_mode_help": "La modalità di esecuzione del server deve essere 'HYBRID', 'DISPATCH_ONLY' o 'GAME_ONLY'. Impossibile avviare Grasscutter...",
"create_resources": "Creazione cartella risorse...",
"resources_error": "Inserisci una copia di 'BinOutput' e 'ExcelBinOutput' nella cartella delle risorse.",
"version": "Versione Grassscutter: %s-%s",
"game_version": "Versione del gioco: %s",
"resources": {
"loading": "Caricamento risorse...",
"finish": "Terminato il caricamento delle risorse."
}
}
},
"commands": {
"generic": {
"not_specified": "Nessun comando specificato.",
"unknown_command": "Comando sconosciuto: %s",
"permission_error": "Non hai il permesso per eseguire questo comando.",
"console_execute_error": "Questo comando può essere eseguito solo dalla console.",
"player_execute_error": "Esegui questo comando nel gioco.",
"command_exist_error": "Nessun comando trovato.",
"no_usage_specified": "Nessun utilizzo specificato",
"no_description_specified": "Nessuna descrizione specificata",
"set_to": "%s impostato su %s.",
"set_for_to": "%s per %s impostato su %s.",
"invalid": {
"amount": "Importo non valido.",
"artifactId": "ID artefatto non valido.",
"avatarId": "ID avatar non valido.",
"avatarLevel": "Livello avatar non valido.",
"entityId": "ID entità non valido.",
"itemId": "ID articolo non valido.",
"itemLevel": "ItemLevel non valido.",
"itemRefinement": "Raffinamento articolo non valido.",
"statValue": "Valore statistica non valido.",
"value_between": "Valore non valido: %s deve essere compreso tra %s e %s.",
"playerId": "ID giocatore non valido.",
"uid": "UID non valido.",
"id": "ID non valido."
}
},
"execution": {
"usage_prefix": "Utilizzo: ",
"player_exist_error": "Giocatore non trovato.",
"player_offline_error": "Il giocatore non è online.",
"item_player_exist_error": "Elemento o UID non valido.",
"player_exist_offline_error": "Il giocatore non è stato trovato o non è online.",
"argument_error": "Argomenti non validi.",
"clear_target": "Obiettivo cancellato.",
"set_target": "I comandi successivi avranno come bersaglio @%s per impostazione predefinita.",
"set_target_online": "@%s è online. Alcuni comandi potrebbero richiedere un bersaglio offline.",
"set_target_offline": "@%s è offline. Alcuni comandi potrebbero richiedere un bersaglio online.",
"need_target": "Questo comando richiede un UID bersaglio. Aggiungi un argomento <@UID> o imposta un bersaglio persistente con /target @UID.",
"need_target_online": "Questo comando richiede un UID bersaglio online, ma il bersaglio corrente è offline. Aggiungi un argomento <@UID> diverso o imposta un target persistente con /target @UID.",
"need_target_offline": "Questo comando richiede un UID bersaglio offline, ma il bersaglio corrente è online. Aggiungi un argomento <@UID> diverso o imposta un target persistente con /target @UID."
},
"status": {
"enabled": "Abilitato",
"disabled": "Disabilitato",
"help": "Aiuto",
"success": "Successo"
},
"account": {
"invalid": "UID non valido.",
"exists": "Esiste già un account con questo nome utente e/o UID.",
"create": "Account creato con UID %s.",
"delete": "Account cancellato.",
"no_account": "Account non trovato.",
"description": "Modifica account utente"
},
"announce": {
"send_success": "Invia un annuncio con successo, puoi revocarlo /a revoca %s.",
"refresh_success": "Aggiorna il file di configurazione dell'annuncio con successo. [Totale %s]",
"revoke_done": "Prova a revocare l'annuncio %s.",
"not_found": "Impossibile trovare l'annuncio %s.",
"description": "Invia annuncio a tutti i giocatori online o gestisci l'annuncio del server"
},
"clear": {
"weapons": "Armi cancellate per %s.",
"artifacts": "Ripuliti artefatti per %s.",
"materials": "Materiali cancellati per %s.",
"furniture": "Mobili cancellati per %s.",
"displays": "Display cancellati per %s.",
"virtuals": "Virtuali cancellate per %s.",
"everything": "Cancellato tutto per %s.",
"description": "Elimina gli oggetti sbloccati non equipaggiati dal tuo inventario. Il valore predefinito è 4* livello 1 raffinamento 1 o inferiore, ma può essere impostato su un livello superiore." },
"coop": {
"success": "Convocato %s nel mondo di %s.",
"description": "Forza qualcuno a unirsi al mondo degli altri. Se nessuno viene preso di mira, ti manda comunque in modalità cooperativa."
},
"enter_dungeon": {
"changed": "Cambiato nel dungeon %s.",
"not_found_error": "Il dungeon non esiste.",
"in_dungeon_error": "Sei già in quel dungeon.",
"description": "Entra in un dungeon"
},
"give": {
"usage_relic": "Utilizzo: fornire <artifactID> [mainPropID] [<appendPropID>[,<times>]]... [lv<livello 0-20>]",
"illegal_relic": "Questo ID artefatto appartiene a un intervallo nella blacklist, potrebbe non essere quello che volevi.",
"given": "Dato %s di %s a %s.",
"given_with_level_and_refinement": "Dato %s con livello %s, perfezionamento %s %s volte a %s.",
"given_level": "Dato %s con livello %s %s volte a %s.",
"given_avatar": "Dato %s con livello da %s a %s.",
"giveall_success": "Ha dato tutti gli oggetti con successo.",
"description": "Dà un oggetto a te o al giocatore specificato. Può anche dare tutte le armi, avatar e/o materiali, e può costruire artefatti personalizzati."
},
"heal": {
"success": "Tutti i personaggi sono stati curati.",
"description": "Guarisci tutti i personaggi della tua squadra attuale."
},
"help": {
"aliases": "Alias: ",
"available_commands": "Comandi disponibili: ",
"tip_need_permission": "Autorizzazione: ",
"tip_need_no_permission": "Nessun Elemento",
"tip_permission_targeted": " (L'autorizzazione %s è richiesta anche per l'uso su altri giocatori)",
"warn_player_has_no_permission": "Avviso: non hai il permesso per eseguire questo comando.",
"description": "Invia il messaggio di aiuto o mostra le informazioni su un comando specificato"
},
"kick": {
"player_kick_player": "Il giocatore [%s:%s] ha espulso un giocatore [%s:%s]",
"server_kick_player": "Ha preso a calci il giocatore [%s:%s]...",
"description": "Espelle il giocatore specificato dal server (WIP)"
},
"killall": {
"scene_not_found_in_player_world": "Scena non trovata nel mondo del giocatore.",
"kill_monsters_in_scene": "Uccidere %s mostri nella scena %s.",
"description": "Uccidi tutte le entità"
},
"killCharacter": {
"success": "Hai ucciso il personaggio attuale di %s.",
"description": "Uccide il personaggio attuale del giocatore"
},
"language": {
"current_language": "La lingua attuale è %s.",
"language_changed": "Lingua modificata in %s.",
"language_not_found": "Attualmente, il server non ha quella lingua.",
"description": "Mostra o cambia la lingua corrente"
},
"list": {
"success": "Ci sono %s player(s) online:",
"description": "Lista player online"
},
"permission": {
"add": "Autorizzazione aggiunta.",
"has_error": "Hanno già questa autorizzazione!",
"remove": "Autorizzazione rimossa.",
"not_have_error": "Non hanno questa autorizzazione!",
"account_error": "Impossibile trovare l'account.",
"description": "Concede o rimuove un'autorizzazione per un utente"
},
"position": {
"success": "Coordinate: %s, %s, %s\nRotazione:%s, %s, %s\nID scena: %a",
"description": "Ottiene informazioni sulla posizione e sulla rotazione"
},
"quest": {
"added": "Missione %s aggiunta.",
"finished": "Missione completata %s.",
"not_found": "Missione non trovata.",
"invalid_id": "ID missione non valido.",
"description": "Aggiungi o completa missioni"
},
"reload": {
"reload_start": "Ricarica configurazione.",
"reload_done": "Ricarica completa.",
"description": "Ricarica configurazione server"
},
"resetConst": {
"reset_all": "Ripristina le costellazioni di tutti gli avatar.",
"success": "Le costellazioni per %s sono state reimpostate. Effettua nuovamente il login per vedere le modifiche.",
"description": "Reimposta il livello della costellazione sul tuo personaggio attivo corrente, dovrai reloggare dopo aver usato il comando per vedere eventuali modifiche"
},
"resetShopLimit": {
"success": "Reset completato.",
"description": "Reimposta il tempo di aggiornamento del negozio del giocatore bersaglio"
},
"sendMail": {
"give_usage": "Utilizzo: give <player> <itemID|itemName> [importo] [livello] [raffinamento]",
"user_not_exist": "L'utente con ID '%s' non esiste.",
"start_composition": "Composizione iniziale del messaggio.\nPer favore usa '/sendmail <titolo>' per continuare.\nPuoi usare '/sendmail stop' in qualsiasi momento.",
"templates": "I modelli di posta saranno presto implementati...",
"invalid_arguments": "Argomenti non validi.",
"send_cancel": "Invio messaggio annullato.",
"send_done": "Messaggio inviato all'utente %s!",
"send_all_done": "Messaggio inviato a tutti gli utenti!",
"not_composition_end": "Composizione del messaggio non nella fase finale.\nPer favore usa '/sendmail %s' o '/sendmail stop' per annullare",
"please_use": "Per favore usa '/sendmail %s'",
"set_title": "Titolo del messaggio impostato come '%s'.\nUsa '/sendmail <content>' per continuare.",
"set_contents": "Contenuto del messaggio impostato come '%s'.\nUsa '/sendmail <sender>' per continuare.",
"set_message_sender": "Mittente del messaggio impostato come '%s'.\nUsa '/sendmail <itemID|itemName|finish> [amount] [level]' per continuare.",
"send": "Allegati %s di %s (livello %s) al messaggio.\nContinua ad aggiungere altri elementi o usa '/sendmail finish' per inviare il messaggio.",
"invalid_arguments_please_use": "Argomenti non validi.\n Utilizza '/sendmail %s'",
"title": "<titolo>",
"message": "<messaggio>",
"sender": "<mittente>",
"arguments": "<itemID|itemName|finitura> [importo] [livello]",
"error": "ERRORE: fase di costruzione %s non valida. Controlla lo stacktrace della console.",
"description": "Invia posta all'utente specificato. L'utilizzo di questo comando cambia in base al suo stato di composizione"
},
"sendMessage": {
"success": "Messaggio inviato.",
"description": "Invia un messaggio a un giocatore come server. Se usato senza target, invia a tutti i giocatori sul server."
},
"setConst": {
"range_error": "Il livello della costellazione deve essere compreso tra 0 e 6.",
"level_error": "Livello costellazione non valido.",
"fail": "Impossibile impostare la costellazione.",
"failed_success": "Le costellazioni per %s sono state impostate su %s. Ricarica la scena per vedere le modifiche.",
"success": "Le costellazioni per %s sono state impostate su %s.",
"successall": "Le costellazioni per tutti i personaggi sono state impostate su %s.",
"description": "Imposta il livello di costellazione per il tuo attuale personaggio attivo"
},
"setFetterLevel": {
"range_error": "Il livello di restrizione deve essere compreso tra 0 e 10.",
"success": "Livello di restrizione impostato su %s.",
"level_error": "Livello restrizione non valido.",
"description": "Imposta il tuo livello di restrizione per il tuo attuale personaggio attivo"
},
"setProp": {
"description": "Imposta le proprietà dell'intero account. Cose come godmode possono essere abilitate in questo modo, oltre a cambiare cose come il pavimento dell'abisso sbloccato e il progresso del pass battaglia.\n\tValori per <prop> (senza distinzione tra maiuscole e minuscole): GodMode | UnlimitedStamina | UnlimitedEnergy | TowerLevel | WorldLevel | BPLevel | SetOpenState | UnsetOpenState | UnlockMap\n\t(cont.) vedi PlayerProperty enum per altri possibili valori, nella forma PROP_MAX_SPRING_VOLUME -> max_spring_volume"
},
"setStats": {
"description": "Imposta la proprietà di combattimento per il tuo personaggio attivo corrente\n\tValori per <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys",
"locked_to": "%s bloccato su %s.",
"locked_for_to": "%s per %s bloccato su %s.",
"unlocked": "%s sbloccato.",
"unlocked_for": "%s per %s sbloccato."
},
"spawn": {
"success": "Evocati %s di %s.",
"limit_reached": "Limite di evocazione della scena raggiunto. Generazione invece di %s entità.",
"description": "Evoca una entità vicino a te"
},
"stop": {
"success": "Server in chiusura...",
"description": "Arresta il server"
},
"talent": {
"out_of_range": "Livello talento non valido. Il livello dovrebbe essere compreso tra 1 e 15.",
"set_id": "Imposta talento %s - \"%s\" su %s.",
"id_desc": "Talenti %s - \"%s\" - \"%s\"",
"invalid_skill_id": "ID talento non valido.",
"invalid_level": "Livello talento non valido.",
"normal_attack_id": "ID attacco normale %s.",
"e_skill_id": "ID talento E %s.",
"q_skill_id": "ID talento Q %s.",
"description": "Imposta il livello di talento per il tuo attuale personaggio attivo"
},
"team": {
"invalid_usage": "Utilizzo non valido.",
"invalid_index": "L'indice non è valido.",
"add_too_much": "Il server ti permette di avere al massimo %s avatar nella tua squadra.",
"failed_to_add_avatar": "Impossibile aggiungere l'ID avatar %s.",
"failed_to_parse_index": "Impossibile analizzare l'indice: %s",
"remove_too_much": "Non puoi rimuovere tutti i tuoi avatar.",
"ignore_index": "Indici ignorati: %s",
"index_out_of_range": "L'indice che hai specificato non è compreso nell'intervallo.",
"failed_parse_avatar_id": "Impossibile analizzare l'ID avatar: %s",
"avatar_already_in_team": "Avatar è già nel team.",
"avatar_not_found": "Avatar %s non trovato.",
"description": "Modifica manualmente la tua squadra."
},
"teleportAll": {
"success": "Evoca tutti i giocatori nella tua posizione.",
"error": "Puoi usare questo comando solo in modalità MP.",
"description": "Teletrasporta tutti i giocatori del tuo mondo nella tua posizione"
},
"teleport": {
"invalid_position": "Posizione non valida.",
"exists_error": "La scena specificata non esiste.",
"success": "Teletrasportato %s in %s, %s, %s nella scena %s.",
"description": "Cambia la posizione del giocatore"
},
"weather": {
"success": "Imposta l'ID meteo su %s con il tipo di clima %s.",
"status": "L'ID meteo attuale è %s con il tipo di clima %s.",
"description": "Cambia l'ID meteo e il tipo di clima. Gli ID meteo possono essere trovati in ./Resources/ExcelBinOutput/WeatherExcelConfigData.json.\nTipi di clima: soleggiato, nuvoloso, pioggia, temporale, neve, nebbia"
},
"ban": {
"success": "Soccesso.",
"failure": "Fallito, giocatore non trovato.",
"invalid_time": "Impossibile analizzare il timestamp.",
"description": "Banna un giocatore"
},
"unlockall": {
"success": "Sbloccato tutti gli stati aperti per %s.",
"description": "Sblocca tutti gli stati aperti per un giocatore."
},
"unban": {
"success": "Successo.",
"failure": "Fallito, giocatore non trovato.",
"description": "Sbanna un giocatore"
}
},
"gacha": {
"details": {
"title": "Dettagli banner",
"available_five_stars": "Articoli disponibili a 5 stelle",
"available_four_stars": "Articoli disponibili a 4 stelle",
"available_three_stars": "Articoli a 3 stelle disponibili"
},
"records": {
"title": "Gacha Records",
"date": "Data",
"item": "oggetto"
}
},
"documentation": {
"handbook": {
"title": "Manuale GM",
"title_commands": "Comandi",
"title_avatars": "Avatar",
"title_items": "Articoli",
"title_scenes": "Scene",
"title_monsters": "Mostri",
"header_id": "ID",
"header_command": "Comando",
"header_description": "Descrizione",
"header_avatar": "Avatar",
"header_item": "Articolo",
"header_scene": "Scena",
"header_monster": "Mostro"
},
"index": {
"title": "Documentazione",
"handbook": "Manuale GM",
"gacha_mapping": "Mappatura Gacha JSON"
}
},
"plugin": {
"directory_failed": "Impossibile creare la directory dei plugin: ",
"unable_to_load": "Impossibile caricare il plug-in.",
"invalid_config": "Il plug-in %s ha un file di configurazione non valido.",
"invalid_main_class": "Il plug-in %s ha una classe principale non valida.",
"missing_config": "Il plug-in %s non ha un file di configurazione valido.",
"failed_to_load_plugin": "Impossibile caricare il plug-in: %s",
"failed_to_load": "Impossibile caricare un plug-in.",
"failed_to_load_dependencies": "Impossibile caricare i plugin con le dipendenze.",
"loading_plugin": "Caricamento plug-in: %s",
"failed_add_id": "Impossibile aggiungere l'identificatore del plug-in: %s",
"enabling_plugin": "Abilitazione plug-in: %s",
"enabling_failed": "Impossibile abilitare il plug-in: %s",
"disabling_plugin": "Disabilitazione plug-in: %s",
"disabling_failed": "Impossibile disabilitare il plug-in: %s"
}
}

View File

@ -1,7 +1,7 @@
{
"messages": {
"game": {
"address_bind": "🇺🇸Game Server started at \u001b[1m\u001b[33m%s:%s\u001b[0m",
"address_bind": "게임 서버가 \u001b[1m\u001b[33m%s:%s\u001b[0m에서 열렸습니다.",
"port_bind": "게임 서버가 포트 %s에서 열렸습니다.",
"connect": "클라이언트가 %s에서 연결됐습니다.",
"disconnect": "클라이언트가 %s에서 연결이 끊겼습니다.",
@ -9,9 +9,9 @@
"command_error": "명령어 오류:"
},
"dispatch": {
"address_bind": "🇺🇸[Dispatch] Dispatch server started at \u001b[1m\u001b[33m%s:%s\u001b[0m",
"address_bind": "[Dispatch] 디스패치 서버가 \u001b[1m\u001b[33m%s:%s\u001b[0m에서 열렸습니다.",
"port_bind": "[Dispatch] 디스패치 서버가 포트 %s에서 열렸습니다.",
"request": "🇺🇸[Dispatch] Client %s %s request: %s",
"request": "[Dispatch] 클라이언트 %s %s 응답: %s",
"keystore": {
"general_error": "[Dispatch] 키스토어 로딩중 오류가 발생했습니다!",
"password_error": "[Dispatch] 키스토어를 로딩할 수 없습니다. 기본 키스토어 암호로 시도 중...",
@ -19,10 +19,10 @@
"default_password": "[Dispatch] 기본 키스토어 암호가 성공적으로 로딩됐습니다. config.json에서 암호를 123456으로 설정해 보십시오."
},
"authentication": {
"default_unable_to_verify": "🇺🇸[Authentication] Something called the verifyUser method which is unavailable in the default authentication handler."
"default_unable_to_verify": "[Authentication] 검증된 유저. 기본 인증 처리기에서 사용할 수 없는 매서드입니다."
},
"no_commands_error": "디스패치 전용 모드에서는 명령어가 지원되지 않습니다.",
"unhandled_request_error": "🇺🇸[Dispatch] Potential unhandled %s request: %s.",
"unhandled_request_error": "[Dispatch] 처리되지 않은 가능성 %s 응답: %s.",
"account": {
"login_attempt": "[Dispatch] %s 클라이언트에서 로그인을 시도하고 있습니다.",
"login_success": "[Dispatch] %s 클라이언트가 %s로 로그인 했습니다.",
@ -136,8 +136,8 @@
"artifacts": "%s의 성유물을 초기화했습니다.",
"materials": "%s의 재료를 초기화했습니다.",
"furniture": "%s의 가구를 초기화했습니다.",
"displays": "🇺🇸Cleared displays for %s.",
"virtuals": "🇺🇸Cleared virtuals for %s.",
"displays": "%s의 Display를 초기화했습니다.",
"virtuals": "%s의 Virtuals를 초기화했습니다.",
"everything": "%s의 모든 아이템을 초기화했습니다.",
"description": "인벤토레에서 잠금된 물건을 제외한 항목을 삭제합니다. 기본적으로 4성, 1레벨, 1재련보다 낮은것만을 기준으로 하지만, 더 높게 설정할 수 있습니다."
},
@ -155,9 +155,9 @@
"usage_relic": "사용법: give <성유물ID> [mainPropID] [<appendPropID>[,<times>]]... [lv<레벨 0-20>]",
"illegal_relic": "이 성유물ID는 블랙리스트 범위에 있습니다. 원하는 ID가 아닐 수 있습니다.",
"given": "%s개의 %s를 %s에게 지급했습니다.",
"given_with_level_and_refinement": "Give %s with level %s, refinement %s %s times to %s.",
"given_level": "🇺🇸Given %s with level %s %s times to %s.",
"given_avatar": "🇺🇸Given %s with level %s to %s.",
"given_with_level_and_refinement": "%s을 %s레벨, %s재련, %s번 %s에게 지급했습니다.",
"given_level": "%s을 레벨 %s %s번 %s에게 지급했습니다.",
"given_avatar": "%s을 레벨 %s로 %s에게 지급했습니다.",
"giveall_success": "모든 아이템이 성공적으로 지급되었습니다.",
"description": "지정된 플레이어가 아이템을 획득합니다. 모든 무기, 캐릭터, 재료를 제공할 수 있으며, 맞춤 성유물을 제공할 수도 있습니다."
},
@ -232,10 +232,10 @@
"description": "대상 플레이어의 상점의 새로고침 시간을 초기화합니다"
},
"sendMail": {
"give_usage": "🇺🇸Usage: give <player> <itemID|itemName> [amount] [level] [refinement]",
"give_usage": "사용법: give <player> <itemID|itemName> [amount] [level] [refinement]",
"user_not_exist": "아이디가 '%s'인 사용자가 없습니다.",
"start_composition": "메세지 작성 시작중.\n계속하려면 '/sendmail <title>'을 입력하십시오.\n'/sendmail stop'도 언제든지 사용할 수 있습니다.",
"templates": "🇺🇸Mail templates coming soon implemented...",
"templates": "곧 구현될 메일 템플릿...",
"invalid_arguments": "잘못된 인수입니다.",
"send_cancel": "메세지 전송이 취소되었습니다",
"send_done": "%s에게 메세지가 전송되었습니다!",
@ -247,11 +247,11 @@
"set_message_sender": "메세지 발송자가 '%s'으로 설정되었습니다.\n계속하려면 '/sendmail <itemID|itemName|finish> [amount] [level]'을 사용하십시오.",
"send": "%s 의 %s을 (레벨 %s)을 메세지에 첨부했습니다.\n계속 항목을 추가하거나, '/sendmail finish'을 사용해 메세지를 보낼 수 있습니다..",
"invalid_arguments_please_use": "잘못된 인수입니다.\n '/sendmail %s'을 사용하십시오",
"title": "🇺🇸<title>",
"message": "🇺🇸<message>",
"sender": "🇺🇸<sender>",
"arguments": "🇺🇸<itemID|itemName|finish> [amount] [level]",
"error": "오류: Invalid construction stage %s. Check console for stacktrace.",
"title": "<title>",
"message": "<message>",
"sender": "<sender>",
"arguments": "<itemID|itemName|finish> [amount] [level]",
"error": "오류: 잘못된 시공 단계 %s. Console에서 스택을 확인하십시오.",
"description": "지정된 사용자에게 메세지를 보냅니다. 이 명령어의 사용법은 세부 내용에 따라 달라집니다."
},
"sendMessage": {
@ -259,13 +259,13 @@
"description": "서버가 플레이어에게 메세지를 전송합니다. 목표를 지정하지 않고 명령어를 사용하는 경우, 서버의 모든 인원에게 메세지가 발송됩니다."
},
"setConst": {
"range_error": "🇺🇸Constellation level must be between 0 and 6.",
"level_error": "🇺🇸Invalid constellation level.",
"fail": "🇺🇸Failed to set constellation.",
"failed_success": "🇺🇸Constellations for %s have been set to %s. Please reload scene to see changes.",
"success": "🇺🇸Constellations for %s have been set to %s.",
"successall": "🇺🇸Constellations for all characters have been set to %s.",
"description": "🇺🇸Sets constellation level for your current active character"
"range_error": "별자리 레벨은 0과 6사이여야 합니다.",
"level_error": "잘못된 별자리 레벨.",
"fail": "별자리 설정에 실패했습니다..",
"failed_success": "%s에 대한 별자리가 %s로 설정되었습니다. 변경 사항을 보려면 Scene를 다시 로드하세요.",
"success": "%s의 별자리가 %s로 설정되었습니다.",
"successall": "모든 캐릭터의 별자리가 %s로 설정되었습니다.",
"description": "현재 캐릭터의 별자리가 정상적으로 설정되었습니다"
},
"setFetterLevel": {
"range_error": "호감도 지수는 0 과 10 사이에 위치해야합니다.",
@ -278,10 +278,10 @@
},
"setStats": {
"description": "당신의 현재 캐릭터의 스텟들을 조절합니다.",
"locked_to": "🇺🇸%s locked to %s.",
"locked_for_to": "🇺🇸%s for %s locked to %s.",
"unlocked": "🇺🇸%s unlocked.",
"unlocked_for": "🇺🇸%s for %s unlocked."
"locked_to": "%s가 %s로 잠겨있습니다.",
"locked_for_to": "%s의 %s가 %s로 잠겨있습니다.",
"unlocked": "%s 잠금해제됨.",
"unlocked_for": "%s의 %s 잠금이 해제됩니다."
},
"spawn": {
"success": " %s 개의 %s 를 소환하는데 성공했습니다.",
@ -293,9 +293,9 @@
"description": "서버를 중지합니다"
},
"talent": {
"out_of_range": "🇺🇸Invalid talent level. Level should be in range of 1-15.",
"set_id": "🇺🇸Set talent %s - \"%s\" to %s.",
"id_desc": "🇺🇸Talent %s - \"%s\" - \"%s\"",
"out_of_range": "불분명한 특성 레벨. 특성의 레벨은 16 미만이여야합니다.",
"set_id": "특성 설정 %s - \"%s\" to %s.",
"id_desc": "특성 %s - \"%s\" - \"%s\"",
"invalid_skill_id": "잘못된 스킬ID.",
"invalid_level": "불분명한 스킬 레벨.",
"normal_attack_id": "기본공격 ID %s.",
@ -310,7 +310,7 @@
"failed_to_add_avatar": "추가하는데 실패했습니다 캐릭터 ID %s.",
"failed_to_parse_index": "분석에 실패함 index: %s",
"remove_too_much": "팀에는 최소 1명의 캐릭터가 편성 되어야 합니다.",
"ignore_index": "🇺🇸Ignored index(es): %s",
"ignore_index": "무시된 인덱스(들): %s",
"index_out_of_range": "지정된 목차는 범위 밖에 있습니다.",
"failed_parse_avatar_id": "분석에 실패한 캐릭터 ID: %s",
"avatar_already_in_team": "해당 캐릭터는 이미 팀에 포함되어 있습니다.",
@ -379,25 +379,25 @@
"header_monster": "몬스터"
},
"index": {
"title": "🇺🇸Documentation",
"handbook": "🇺🇸GM Handbook",
"gacha_mapping": "🇺🇸Gacha mapping JSON"
"title": "문서",
"handbook": "GM Handbook",
"gacha_mapping": "Gacha mapping JSON"
}
},
"plugin": {
"directory_failed": "🇺🇸Failed to create plugins directory: ",
"unable_to_load": "🇺🇸Unable to load plugin.",
"invalid_config": "🇺🇸Plugin %s has an invalid config file.",
"invalid_main_class": "🇺🇸Plugin %s has an invalid main class.",
"missing_config": "🇺🇸Plugin %s lacks a valid config file.",
"failed_to_load_plugin": "🇺🇸Failed to load plugin: %s",
"failed_to_load": "🇺🇸Failed to load a plugin.",
"failed_to_load_dependencies": "🇺🇸Failed to load plugins with dependencies.",
"loading_plugin": "🇺🇸Loading plugin: %s",
"failed_add_id": "🇺🇸Failed to add plugin identifier: %s",
"enabling_plugin": "🇺🇸Enabling plugin: %s",
"enabling_failed": "🇺🇸Failed to enable plugin: %s",
"disabling_plugin": "🇺🇸Disabling plugin: %s",
"disabling_failed": "🇺🇸Failed to disable plugin: %s"
"directory_failed": "플러그인 디렉토리를 생성하지 못했습니다: ",
"unable_to_load": "플러그인을 로드할 수 없습니다.",
"invalid_config": "%s플러그인에 잘못된 구성 파일이 있습니다.",
"invalid_main_class": "%s플러그인에 잘못된 메인 클래스가 있습니다.",
"missing_config": "%s플러그인에 올바른 구성 파일이 없습니다.",
"failed_to_load_plugin": "플러그인을 로드하는데 실패했습니다: %s",
"failed_to_load": "플러그인을 로드하는데 실패했습니다.",
"failed_to_load_dependencies": "종속성 플러그인을 로드하지 못했습니다.",
"loading_plugin": "플러그인을 로드하는중: %s",
"failed_add_id": "플러그인의 식별자를 추가하지 못했습니다: %s",
"enabling_plugin": "플러그인을 활성화했습니다: %s",
"enabling_failed": "플러그인을 활성화하는데 실패했습니다: %s",
"disabling_plugin": "플러그인을 비활성화했습니다: %s",
"disabling_failed": "플러그인을 비활성화하는데 실패했습니다: %s"
}
}

View File

@ -264,7 +264,7 @@
"fail": "命之座等级设置失败。",
"failed_success": "命之座 %s 已设置为 %s。",
"success": "命之座 %s 已设置为 %s。",
"successall": "🇺🇸Constellations for all characters have been set to %s.",
"successall": "所有角色的命之座已设置为 %s",
"description": "为当前活跃角色设置命座等级"
},
"setFetterLevel": {