forked from Hay1tsme/artemis
Added support for maimai and Chunithm in Card Maker 1.34/1.35 (#14)
Co-authored-by: Dniel97 <Dniel97@noreply.gitea.tendokyu.moe> Reviewed-on: Hay1tsme/artemis#14 Co-authored-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe> Co-committed-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe>
This commit is contained in:
parent
a791142f95
commit
2af7751504
@ -1,99 +0,0 @@
|
|||||||
CREATE TABLE ongeki_user_gacha (
|
|
||||||
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
|
||||||
user INT NOT NULL,
|
|
||||||
gachaId INT NOT NULL,
|
|
||||||
totalGachaCnt INT DEFAULT 0,
|
|
||||||
ceilingGachaCnt INT DEFAULT 0,
|
|
||||||
selectPoint INT DEFAULT 0,
|
|
||||||
useSelectPoint INT DEFAULT 0,
|
|
||||||
dailyGachaCnt INT DEFAULT 0,
|
|
||||||
fiveGachaCnt INT DEFAULT 0,
|
|
||||||
elevenGachaCnt INT DEFAULT 0,
|
|
||||||
dailyGachaDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT ongeki_user_gacha_uk UNIQUE (user, gachaId),
|
|
||||||
CONSTRAINT ongeki_user_gacha_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ongeki_user_gacha_supply (
|
|
||||||
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
|
||||||
user INT NOT NULL,
|
|
||||||
cardId INT NOT NULL,
|
|
||||||
CONSTRAINT ongeki_user_gacha_supply_uk UNIQUE (user, cardId),
|
|
||||||
CONSTRAINT ongeki_user_gacha_supply_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ongeki_static_gachas (
|
|
||||||
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
|
||||||
version INT NOT NULL,
|
|
||||||
gachaId INT NOT NULL,
|
|
||||||
gachaName VARCHAR(255) NOT NULL,
|
|
||||||
kind INT NOT NULL,
|
|
||||||
type INT DEFAULT 0,
|
|
||||||
isCeiling BOOLEAN DEFAULT 0,
|
|
||||||
maxSelectPoint INT DEFAULT 0,
|
|
||||||
ceilingCnt INT DEFAULT 10,
|
|
||||||
changeRateCnt1 INT DEFAULT 0,
|
|
||||||
changeRateCnt2 INT DEFAULT 0,
|
|
||||||
startDate TIMESTAMP DEFAULT '2018-01-01 00:00:00.0',
|
|
||||||
endDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
|
|
||||||
noticeStartDate TIMESTAMP DEFAULT '2018-01-01 00:00:00.0',
|
|
||||||
noticeEndDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
|
|
||||||
convertEndDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
|
|
||||||
CONSTRAINT ongeki_static_gachas_uk UNIQUE (version, gachaId, gachaName)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ongeki_static_gacha_cards (
|
|
||||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
gachaId INT NOT NULL,
|
|
||||||
cardId INT NOT NULL,
|
|
||||||
rarity INT NOT NULL,
|
|
||||||
weight INT DEFAULT 1,
|
|
||||||
isPickup BOOLEAN DEFAULT 0,
|
|
||||||
isSelect BOOLEAN DEFAULT 1,
|
|
||||||
CONSTRAINT ongeki_static_gacha_cards_uk UNIQUE (gachaId, cardId)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE ongeki_static_cards (
|
|
||||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
version INT NOT NULL,
|
|
||||||
cardId INT NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
charaId INT NOT NULL,
|
|
||||||
nickName VARCHAR(255),
|
|
||||||
school VARCHAR(255) NOT NULL,
|
|
||||||
attribute VARCHAR(5) NOT NULL,
|
|
||||||
gakunen VARCHAR(255) NOT NULL,
|
|
||||||
rarity INT NOT NULL,
|
|
||||||
levelParam VARCHAR(255) NOT NULL,
|
|
||||||
skillId INT NOT NULL,
|
|
||||||
choKaikaSkillId INT NOT NULL,
|
|
||||||
cardNumber VARCHAR(255),
|
|
||||||
CONSTRAINT ongeki_static_cards_uk UNIQUE (version, cardId)
|
|
||||||
) CHARACTER SET utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE ongeki_user_print_detail (
|
|
||||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
user INT NOT NULL,
|
|
||||||
cardId INT NOT NULL,
|
|
||||||
cardType INT DEFAULT 0,
|
|
||||||
printDate TIMESTAMP NOT NULL,
|
|
||||||
serialId VARCHAR(20) NOT NULL,
|
|
||||||
placeId INT NOT NULL,
|
|
||||||
clientId VARCHAR(11) NOT NULL,
|
|
||||||
printerSerialId VARCHAR(20) NOT NULL,
|
|
||||||
isHolograph BOOLEAN DEFAULT 0,
|
|
||||||
isAutographed BOOLEAN DEFAULT 0,
|
|
||||||
printOption1 BOOLEAN DEFAULT 1,
|
|
||||||
printOption2 BOOLEAN DEFAULT 1,
|
|
||||||
printOption3 BOOLEAN DEFAULT 1,
|
|
||||||
printOption4 BOOLEAN DEFAULT 1,
|
|
||||||
printOption5 BOOLEAN DEFAULT 1,
|
|
||||||
printOption6 BOOLEAN DEFAULT 1,
|
|
||||||
printOption7 BOOLEAN DEFAULT 1,
|
|
||||||
printOption8 BOOLEAN DEFAULT 1,
|
|
||||||
printOption9 BOOLEAN DEFAULT 1,
|
|
||||||
printOption10 BOOLEAN DEFAULT 0,
|
|
||||||
FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT ongeki_user_print_detail_uk UNIQUE (serialId)
|
|
||||||
) CHARACTER SET utf8mb4;
|
|
21
core/data/schema/versions/SDEZ_3_upgrade.sql
Normal file
21
core/data/schema/versions/SDEZ_3_upgrade.sql
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
ALTER TABLE mai2_item_card
|
||||||
|
CHANGE COLUMN card_id cardId INT NOT NULL AFTER user,
|
||||||
|
CHANGE COLUMN card_kind cardTypeId INT NOT NULL,
|
||||||
|
CHANGE COLUMN chara_id charaId INT NOT NULL,
|
||||||
|
CHANGE COLUMN map_id mapId INT NOT NULL,
|
||||||
|
CHANGE COLUMN startDate startDate TIMESTAMP NULL DEFAULT '2018-01-01 00:00:00',
|
||||||
|
CHANGE COLUMN endDate endDate TIMESTAMP NULL DEFAULT '2038-01-01 00:00:00';
|
||||||
|
|
||||||
|
ALTER TABLE mai2_item_item
|
||||||
|
CHANGE COLUMN item_id itemId INT NOT NULL AFTER user,
|
||||||
|
CHANGE COLUMN item_kind itemKind INT NOT NULL,
|
||||||
|
CHANGE COLUMN is_valid isValid TINYINT(1) NOT NULL DEFAULT '1';
|
||||||
|
|
||||||
|
ALTER TABLE mai2_item_character
|
||||||
|
CHANGE COLUMN character_id characterId INT NOT NULL,
|
||||||
|
CHANGE COLUMN use_count useCount INT NOT NULL DEFAULT '0';
|
||||||
|
|
||||||
|
ALTER TABLE mai2_item_charge
|
||||||
|
CHANGE COLUMN charge_id chargeId INT NOT NULL,
|
||||||
|
CHANGE COLUMN purchase_date purchaseDate TIMESTAMP NOT NULL,
|
||||||
|
CHANGE COLUMN valid_date validDate TIMESTAMP NOT NULL;
|
351
docs/game_specific_info.md
Normal file
351
docs/game_specific_info.md
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
# ARTEMiS Games Documentation
|
||||||
|
|
||||||
|
Below are all supported games with supported version ids in order to use
|
||||||
|
the corresponding importer and database upgrades.
|
||||||
|
|
||||||
|
**Important: The described database upgrades are only required if you are using an old database schema, f.e. still
|
||||||
|
using the megaime database. Clean installations always create the latest database structure!**
|
||||||
|
|
||||||
|
# Table of content
|
||||||
|
|
||||||
|
- [Supported Games](#Supported-Games)
|
||||||
|
- [Chunithm](#Chunithm)
|
||||||
|
- [crossbeats REV.](#crossbeats-REV)
|
||||||
|
- [maimai DX](#maimai-DX)
|
||||||
|
- [O.N.G.E.K.I.](#ONGEKI)
|
||||||
|
- [Card Maker](#Card-Maker)
|
||||||
|
- [WACCA](#WACCA)
|
||||||
|
|
||||||
|
|
||||||
|
# Supported Games
|
||||||
|
|
||||||
|
Games listed below have been tested and confirmed working.
|
||||||
|
|
||||||
|
## Chunithm
|
||||||
|
|
||||||
|
### SDBT
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|--------------------|
|
||||||
|
| 0 | Chunithm |
|
||||||
|
| 1 | Chunithm+ |
|
||||||
|
| 2 | Chunithm Air |
|
||||||
|
| 3 | Chunithm Air + |
|
||||||
|
| 4 | Chunithm Star |
|
||||||
|
| 5 | Chunithm Star + |
|
||||||
|
| 6 | Chunithm Amazon |
|
||||||
|
| 7 | Chunithm Amazon + |
|
||||||
|
| 8 | Chunithm Crystal |
|
||||||
|
| 9 | Chunithm Crystal + |
|
||||||
|
| 10 | Chunithm Paradise |
|
||||||
|
|
||||||
|
### SDHD/SDBT
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|-----------------|
|
||||||
|
| 11 | Chunithm New!! |
|
||||||
|
| 12 | Chunithm New!!+ |
|
||||||
|
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDBT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for Chunithm will import: Events, Music, Charge Items and Avatar Accesories.
|
||||||
|
|
||||||
|
### Database upgrade
|
||||||
|
|
||||||
|
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see
|
||||||
|
which version is the latest, f.e. `SDBT_3_upgrade.sql`. In order to upgrade to version 3 in this case you need to
|
||||||
|
perform all previous updates as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python dbutils.py --game SDBT --version 2 upgrade
|
||||||
|
python dbutils.py --game SDBT --version 3 upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## crossbeats REV.
|
||||||
|
|
||||||
|
### SDCA
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|------------------------------------|
|
||||||
|
| 0 | crossbeats REV. |
|
||||||
|
| 1 | crossbeats REV. SUNRISE |
|
||||||
|
| 2 | crossbeats REV. SUNRISE S2 |
|
||||||
|
| 3 | crossbeats REV. SUNRISE S2 Omnimix |
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer you need to use the provided `Export.csv` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDCA --version <version ID> --binfolder titles/cxb/data
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for crossbeats REV. will import Music.
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
Config file is located in `config/cxb.yaml`.
|
||||||
|
|
||||||
|
| Option | Info |
|
||||||
|
|------------------------|------------------------------------------------------------|
|
||||||
|
| `hostname` | Requires a proper `hostname` (not localhost!) to run |
|
||||||
|
| `ssl_enable` | Enables/Disables the use of the `ssl_cert` and `ssl_key` |
|
||||||
|
| `port` | Set your unsecure port number |
|
||||||
|
| `port_secure` | Set your secure/SSL port number |
|
||||||
|
| `ssl_cert`, `ssl_key` | Enter your SSL certificate (requires not self signed cert) |
|
||||||
|
|
||||||
|
|
||||||
|
## maimai DX
|
||||||
|
|
||||||
|
### SDEZ
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|-------------------------|
|
||||||
|
| 0 | maimai DX |
|
||||||
|
| 1 | maimai DX PLUS |
|
||||||
|
| 2 | maimai DX Splash |
|
||||||
|
| 3 | maimai DX Splash PLUS |
|
||||||
|
| 4 | maimai DX Universe |
|
||||||
|
| 5 | maimai DX Universe PLUS |
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDEZ --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for maimai DX will import Events, Music and Tickets.
|
||||||
|
|
||||||
|
**NOTE: It is required to use the importer because the game will
|
||||||
|
crash without it!**
|
||||||
|
|
||||||
|
### Database upgrade
|
||||||
|
|
||||||
|
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDEZ_2_upgrade.sql`. In order to upgrade to version 2 in this case you need to perform all previous updates as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python dbutils.py --game SDEZ --version 2 upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hatsune Miku Project Diva
|
||||||
|
|
||||||
|
### SBZV
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|---------------------------------|
|
||||||
|
| 0 | Project Diva Arcade |
|
||||||
|
| 1 | Project Diva Arcade Future Tone |
|
||||||
|
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SBZV --version <version ID> --binfolder /path/to/game/data/diva --optfolder /path/to/game/data/diva/mdata
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for Project Diva Arcade will all required data in order to use
|
||||||
|
the Shop, Modules and Customizations.
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
Config file is located in `config/diva.yaml`.
|
||||||
|
|
||||||
|
| Option | Info |
|
||||||
|
|----------------------|-------------------------------------------------------------------------------------------------|
|
||||||
|
| `unlock_all_modules` | Unlocks all modules (costumes) by default, if set to `False` all modules need to be purchased |
|
||||||
|
| `unlock_all_items` | Unlocks all items (customizations) by default, if set to `False` all items need to be purchased |
|
||||||
|
|
||||||
|
|
||||||
|
### Database upgrade
|
||||||
|
|
||||||
|
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see
|
||||||
|
which version is the latest, f.e. `SBZV_4_upgrade.sql`. In order to upgrade to version 4 in this case you need to
|
||||||
|
perform all previous updates as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python dbutils.py --game SBZV --version 2 upgrade
|
||||||
|
python dbutils.py --game SBZV --version 3 upgrade
|
||||||
|
python dbutils.py --game SBZV --version 4 upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## O.N.G.E.K.I.
|
||||||
|
|
||||||
|
### SDDT
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|----------------------------|
|
||||||
|
| 0 | O.N.G.E.K.I. |
|
||||||
|
| 1 | O.N.G.E.K.I. + |
|
||||||
|
| 2 | O.N.G.E.K.I. Summer |
|
||||||
|
| 3 | O.N.G.E.K.I. Summer + |
|
||||||
|
| 4 | O.N.G.E.K.I. Red |
|
||||||
|
| 5 | O.N.G.E.K.I. Red + |
|
||||||
|
| 6 | O.N.G.E.K.I. Bright |
|
||||||
|
| 7 | O.N.G.E.K.I. Bright Memory |
|
||||||
|
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for O.N.G.E.K.I. will all all Cards, Music and Events.
|
||||||
|
|
||||||
|
**NOTE: The Importer is required for Card Maker.**
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
Config file is located in `config/ongeki.yaml`.
|
||||||
|
|
||||||
|
| Option | Info |
|
||||||
|
|------------------|----------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `enabled_gachas` | Enter all gacha IDs for Card Maker to work, other than default may not work due to missing cards added to them |
|
||||||
|
|
||||||
|
Note: 1149 and higher are only for Card Maker 1.35 and higher and will be ignored on lower versions.
|
||||||
|
|
||||||
|
### Database upgrade
|
||||||
|
|
||||||
|
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see
|
||||||
|
which version is the latest, f.e. `SDDT_4_upgrade.sql`. In order to upgrade to version 4 in this case you need to
|
||||||
|
perform all previous updates as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python dbutils.py --game SDDT --version 2 upgrade
|
||||||
|
python dbutils.py --game SDDT --version 3 upgrade
|
||||||
|
python dbutils.py --game SDDT --version 4 upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## Card Maker
|
||||||
|
|
||||||
|
### SDED
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|-----------------|
|
||||||
|
| 0 | Card Maker 1.34 |
|
||||||
|
| 1 | Card Maker 1.35 |
|
||||||
|
|
||||||
|
|
||||||
|
### Support status
|
||||||
|
|
||||||
|
* Card Maker 1.34:
|
||||||
|
* Chunithm New!!: Yes
|
||||||
|
* maimai DX Universe: Yes
|
||||||
|
* O.N.G.E.K.I. Bright: Yes
|
||||||
|
|
||||||
|
* Card Maker 1.35:
|
||||||
|
* Chunithm New!!+: Yes
|
||||||
|
* maimai DX Universe PLUS: Yes
|
||||||
|
* O.N.G.E.K.I. Bright Memory: Yes
|
||||||
|
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer you need to use the provided `.csv` files (which are required for O.N.G.E.K.I.) and the
|
||||||
|
option folders:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDED --version <version ID> --binfolder titles/cm/cm_data --optfolder /path/to/cardmaker/option/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
**If you haven't already executed the O.N.G.E.K.I. importer, make sure you import all cards!**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
Also make sure to import all maimai and Chunithm data as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDED --version <version ID> --binfolder /path/to/cardmaker/CardMaker_Data
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for Card Maker will import all required Gachas (Banners) and cards (for maimai/Chunithm) and the hardcoded
|
||||||
|
Cards for each Gacha (O.N.G.E.K.I. only).
|
||||||
|
|
||||||
|
**NOTE: Without executing the importer Card Maker WILL NOT work!**
|
||||||
|
|
||||||
|
|
||||||
|
### O.N.G.E.K.I. Gachas
|
||||||
|
|
||||||
|
Gacha "無料ガチャ" can only pull from the free cards with the following probabilities: 94%: R, 5% SR and 1% chance of
|
||||||
|
getting an SSR card
|
||||||
|
|
||||||
|
Gacha "無料ガチャ(SR確定)" can only pull from free SR cards with prob: 92% SR and 8% chance of getting an SSR card
|
||||||
|
|
||||||
|
Gacha "レギュラーガチャ" can pull from every card added to ongeki_static_cards with the following prob: 77% R, 20% SR
|
||||||
|
and 3% chance of getting an SSR card
|
||||||
|
|
||||||
|
All other (limited) gachas can pull from every card added to ongeki_static_cards but with the promoted cards
|
||||||
|
(click on the green button under the banner) having a 10 times higher chance to get pulled
|
||||||
|
|
||||||
|
### Chunithm Gachas
|
||||||
|
|
||||||
|
All cards in Chunithm (basically just the characters) have the same rarity to it just pulls randomly from all cards
|
||||||
|
from a given gacha but made sure you cannot pull the same card twice in the same 5 times gacha roll.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
Card Maker 1.34 will only load an O.N.G.E.K.I. Bright profile (1.30). Card Maker 1.35 will only load an O.N.G.E.K.I.
|
||||||
|
Bright Memory profile (1.35).
|
||||||
|
The gachas inside the `ongeki.yaml` will make sure only the right gacha ids for the right CM version will be loaded.
|
||||||
|
Gacha IDs up to 1140 will be loaded for CM 1.34 and all gachas will be loaded for CM 1.35.
|
||||||
|
|
||||||
|
**NOTE: There is currently no way to load/use the (printed) maimai DX cards!**
|
||||||
|
|
||||||
|
## WACCA
|
||||||
|
|
||||||
|
### SDFE
|
||||||
|
|
||||||
|
| Version ID | Version Name |
|
||||||
|
|------------|---------------|
|
||||||
|
| 0 | WACCA |
|
||||||
|
| 1 | WACCA S |
|
||||||
|
| 2 | WACCA Lily |
|
||||||
|
| 3 | WACCA Lily R |
|
||||||
|
| 4 | WACCA Reverse |
|
||||||
|
|
||||||
|
|
||||||
|
### Importer
|
||||||
|
|
||||||
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python read.py --series SDFE --version <version ID> --binfolder /path/to/game/WindowsNoEditor/Mercury/Content
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer for WACCA will import all Music data.
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
Config file is located in `config/wacca.yaml`.
|
||||||
|
|
||||||
|
| Option | Info |
|
||||||
|
|--------------------|-----------------------------------------------------------------------------|
|
||||||
|
| `always_vip` | Enables/Disables VIP, if disabled it needs to be purchased manually in game |
|
||||||
|
| `infinite_tickets` | Always set the "unlock expert" tickets to 5 |
|
||||||
|
| `infinite_wp` | Sets the user WP to `999999` |
|
||||||
|
| `enabled_gates` | Enter all gate IDs which should be enabled in game |
|
||||||
|
|
||||||
|
|
||||||
|
### Database upgrade
|
||||||
|
|
||||||
|
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDFE_3_upgrade.sql`. In order to upgrade to version 3 in this case you need to perform all previous updates as well:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python dbutils.py --game SDFE --version 2 upgrade
|
||||||
|
python dbutils.py --game SDFE --version 3 upgrade
|
||||||
|
```
|
6
read.py
6
read.py
@ -4,7 +4,8 @@ import re
|
|||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
from os import path
|
from os import path
|
||||||
import logging, coloredlogs
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@ -134,7 +135,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
for dir, mod in titles.items():
|
for dir, mod in titles.items():
|
||||||
if args.series in mod.game_codes:
|
if args.series in mod.game_codes:
|
||||||
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
|
handler = mod.reader(config, args.version,
|
||||||
|
bin_arg, opt_arg, args.extra)
|
||||||
handler.read()
|
handler.read()
|
||||||
|
|
||||||
logger.info("Done")
|
logger.info("Done")
|
||||||
|
@ -17,7 +17,7 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
|||||||
|
|
||||||
+ Card Maker
|
+ Card Maker
|
||||||
+ 1.34.xx
|
+ 1.34.xx
|
||||||
+ 1.36.xx
|
+ 1.35.xx
|
||||||
|
|
||||||
+ Ongeki
|
+ Ongeki
|
||||||
+ All versions up to Bright Memory
|
+ All versions up to Bright Memory
|
||||||
@ -36,5 +36,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
|||||||
## Setup guides
|
## Setup guides
|
||||||
Follow the platform-specific guides for [windows](docs/INSTALL_WINDOWS.md) and [ubuntu](docs/INSTALL_UBUNTU.md) to setup and run the server.
|
Follow the platform-specific guides for [windows](docs/INSTALL_WINDOWS.md) and [ubuntu](docs/INSTALL_UBUNTU.md) to setup and run the server.
|
||||||
|
|
||||||
|
## Game specific information
|
||||||
|
Read [Games specific info](docs/game_specific_info.md) for all supported games, importer settings, configuration option and database upgrades.
|
||||||
|
|
||||||
## Production guide
|
## Production guide
|
||||||
See the [production guide](docs/prod.md) for running a production server.
|
See the [production guide](docs/prod.md) for running a production server.
|
||||||
|
@ -588,3 +588,11 @@ class ChuniBase:
|
|||||||
|
|
||||||
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
|
||||||
return {"returnCode": "1"}
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
|
def handle_get_user_net_battle_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"userNetBattleData": {
|
||||||
|
"recentNBSelectMusicList": []
|
||||||
|
}
|
||||||
|
}
|
@ -103,7 +103,7 @@ class ChuniServlet:
|
|||||||
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
if url_path.lower() == "/ping":
|
if url_path.lower() == "ping":
|
||||||
return zlib.compress(b'{"returnCode": "1"}')
|
return zlib.compress(b'{"returnCode": "1"}')
|
||||||
|
|
||||||
req_raw = request.content.getvalue()
|
req_raw = request.content.getvalue()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from random import randint
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
@ -122,11 +122,355 @@ class ChuniNew(ChuniBase):
|
|||||||
"playerLevel": profile["playerLevel"],
|
"playerLevel": profile["playerLevel"],
|
||||||
"rating": profile["rating"],
|
"rating": profile["rating"],
|
||||||
"headphone": profile["headphone"],
|
"headphone": profile["headphone"],
|
||||||
"chargeState": 0,
|
# Enables favorites and teams
|
||||||
"userNameEx": "0",
|
"chargeState": 1,
|
||||||
|
"userNameEx": "",
|
||||||
"banState": 0,
|
"banState": 0,
|
||||||
"classEmblemMedal": profile["classEmblemMedal"],
|
"classEmblemMedal": profile["classEmblemMedal"],
|
||||||
"classEmblemBase": profile["classEmblemBase"],
|
"classEmblemBase": profile["classEmblemBase"],
|
||||||
"battleRankId": profile["battleRankId"],
|
"battleRankId": profile["battleRankId"],
|
||||||
}
|
}
|
||||||
return data1
|
return data1
|
||||||
|
|
||||||
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||||
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userName": p["userName"],
|
||||||
|
"level": p["level"],
|
||||||
|
"medal": p["medal"],
|
||||||
|
"lastDataVersion": "2.00.00",
|
||||||
|
"isLogin": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_printer_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_printer_logout_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
returns all current active banners (gachas)
|
||||||
|
"""
|
||||||
|
game_gachas = self.data.static.get_gachas(self.version)
|
||||||
|
|
||||||
|
# clean the database rows
|
||||||
|
game_gacha_list = []
|
||||||
|
for gacha in game_gachas:
|
||||||
|
tmp = gacha._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("version")
|
||||||
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["noticeStartDate"] = datetime.strftime(
|
||||||
|
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
tmp["noticeEndDate"] = datetime.strftime(
|
||||||
|
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
game_gacha_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"length": len(game_gacha_list),
|
||||||
|
"gameGachaList": game_gacha_list,
|
||||||
|
# no clue
|
||||||
|
"registIdList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
returns all valid cards for a given gachaId
|
||||||
|
"""
|
||||||
|
game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"])
|
||||||
|
|
||||||
|
game_gacha_card_list = []
|
||||||
|
for gacha_card in game_gacha_cards:
|
||||||
|
tmp = gacha_card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
game_gacha_card_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"gachaId": data["gachaId"],
|
||||||
|
"length": len(game_gacha_card_list),
|
||||||
|
# check isPickup from the chuni_static_gachas?
|
||||||
|
"isPickup": False,
|
||||||
|
"gameGachaCardList": game_gacha_card_list,
|
||||||
|
# again no clue
|
||||||
|
"emissionList": [],
|
||||||
|
"afterCalcList": [],
|
||||||
|
"ssrBookCalcList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||||
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
profile = p._asdict()
|
||||||
|
profile.pop("id")
|
||||||
|
profile.pop("user")
|
||||||
|
profile.pop("version")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"userData": profile,
|
||||||
|
"userEmoney": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"emoneyCredit": 100,
|
||||||
|
"emoneyBrand": 1,
|
||||||
|
"ext1": 0,
|
||||||
|
"ext2": 0,
|
||||||
|
"ext3": 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_gachas = self.data.item.get_user_gachas(data["userId"])
|
||||||
|
if user_gachas is None:
|
||||||
|
return {"userId": data["userId"], "length": 0, "userGachaList": []}
|
||||||
|
|
||||||
|
user_gacha_list = []
|
||||||
|
for gacha in user_gachas:
|
||||||
|
tmp = gacha._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["dailyGachaDate"] = datetime.strftime(tmp["dailyGachaDate"], "%Y-%m-%d")
|
||||||
|
user_gacha_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": len(user_gacha_list),
|
||||||
|
"userGachaList": user_gacha_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_printed_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_print_list = self.data.item.get_user_print_states(
|
||||||
|
data["userId"], has_completed=True
|
||||||
|
)
|
||||||
|
if user_print_list is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": 0,
|
||||||
|
"nextIndex": -1,
|
||||||
|
"userPrintedCardList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
print_list = []
|
||||||
|
next_idx = int(data["nextIndex"])
|
||||||
|
max_ct = int(data["maxCount"])
|
||||||
|
|
||||||
|
for x in range(next_idx, len(user_print_list)):
|
||||||
|
tmp = user_print_list[x]._asdict()
|
||||||
|
print_list.append(tmp["cardId"])
|
||||||
|
|
||||||
|
if len(user_print_list) >= max_ct:
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(user_print_list) >= max_ct:
|
||||||
|
next_idx = next_idx + max_ct
|
||||||
|
else:
|
||||||
|
next_idx = -1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": len(print_list),
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userPrintedCardList": print_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
|
||||||
|
user_print_states = self.data.item.get_user_print_states(
|
||||||
|
user_id, has_completed=False
|
||||||
|
)
|
||||||
|
|
||||||
|
card_print_state_list = []
|
||||||
|
for card in user_print_states:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp["orderId"] = tmp["id"]
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["limitDate"] = datetime.strftime(tmp["limitDate"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
card_print_state_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": user_id,
|
||||||
|
"length": len(card_print_state_list),
|
||||||
|
"userCardPrintStateList": card_print_state_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
|
return super().handle_get_user_character_api_request(data)
|
||||||
|
|
||||||
|
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
return super().handle_get_user_item_api_request(data)
|
||||||
|
|
||||||
|
def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
Handle a gacha roll API request, with:
|
||||||
|
gachaId: the gachaId where the cards should be pulled from
|
||||||
|
times: the number of gacha rolls
|
||||||
|
characterId: the character which the user wants
|
||||||
|
"""
|
||||||
|
gacha_id = data["gachaId"]
|
||||||
|
num_rolls = data["times"]
|
||||||
|
chara_id = data["characterId"]
|
||||||
|
|
||||||
|
rolled_cards = []
|
||||||
|
|
||||||
|
# characterId is set after 10 rolls, where the user can select a card
|
||||||
|
# from all gameGachaCards, therefore the correct cardId for a given
|
||||||
|
# characterId should be returned
|
||||||
|
if chara_id != -1:
|
||||||
|
# get the
|
||||||
|
card = self.data.static.get_gacha_card_by_character(gacha_id, chara_id)
|
||||||
|
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
|
||||||
|
rolled_cards.append(tmp)
|
||||||
|
else:
|
||||||
|
gacha_cards = self.data.static.get_gacha_cards(gacha_id)
|
||||||
|
|
||||||
|
# get the card id for each roll
|
||||||
|
for _ in range(num_rolls):
|
||||||
|
# get the index from all possible cards
|
||||||
|
card_idx = randint(0, len(gacha_cards) - 1)
|
||||||
|
# remove the index from the cards so it wont get pulled again
|
||||||
|
card = gacha_cards.pop(card_idx)
|
||||||
|
|
||||||
|
# remove the "id" fronm the card
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
|
||||||
|
rolled_cards.append(tmp)
|
||||||
|
|
||||||
|
return {"length": len(rolled_cards), "gameGachaCardList": rolled_cards}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_gacha_api_request(self, data: Dict) -> Dict:
|
||||||
|
upsert = data["cmUpsertUserGacha"]
|
||||||
|
user_id = data["userId"]
|
||||||
|
place_id = data["placeId"]
|
||||||
|
|
||||||
|
# save the user data
|
||||||
|
user_data = upsert["userData"]
|
||||||
|
user_data.pop("rankUpChallengeResults")
|
||||||
|
user_data.pop("userEmoney")
|
||||||
|
|
||||||
|
self.data.profile.put_profile_data(user_id, self.version, user_data)
|
||||||
|
|
||||||
|
# save the user gacha
|
||||||
|
user_gacha = upsert["userGacha"]
|
||||||
|
gacha_id = user_gacha["gachaId"]
|
||||||
|
user_gacha.pop("gachaId")
|
||||||
|
user_gacha.pop("dailyGachaDate")
|
||||||
|
|
||||||
|
self.data.item.put_user_gacha(user_id, gacha_id, user_gacha)
|
||||||
|
|
||||||
|
# save all user items
|
||||||
|
if "userItemList" in upsert:
|
||||||
|
for item in upsert["userItemList"]:
|
||||||
|
self.data.item.put_item(user_id, item)
|
||||||
|
|
||||||
|
# add every gamegachaCard to database
|
||||||
|
for card in upsert["gameGachaCardList"]:
|
||||||
|
self.data.item.put_user_print_state(
|
||||||
|
user_id,
|
||||||
|
hasCompleted=False,
|
||||||
|
placeId=place_id,
|
||||||
|
cardId=card["cardId"],
|
||||||
|
gachaId=card["gachaId"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# retrieve every game gacha card which has been added in order to get
|
||||||
|
# the orderId for the next request
|
||||||
|
user_print_states = self.data.item.get_user_print_states_by_gacha(
|
||||||
|
user_id, gacha_id, has_completed=False
|
||||||
|
)
|
||||||
|
card_print_state_list = []
|
||||||
|
for card in user_print_states:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp["orderId"] = tmp["id"]
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["limitDate"] = datetime.strftime(tmp["limitDate"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
card_print_state_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": "1",
|
||||||
|
"apiName": "CMUpsertUserGachaApi",
|
||||||
|
"userCardPrintStateList": card_print_state_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": "11111111111111111111",
|
||||||
|
"apiName": "CMUpsertUserPrintlogApi",
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_print_detail = data["userPrintDetail"]
|
||||||
|
user_id = data["userId"]
|
||||||
|
|
||||||
|
# generate random serial id
|
||||||
|
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
||||||
|
|
||||||
|
# not needed because are either zero or unset
|
||||||
|
user_print_detail.pop("orderId")
|
||||||
|
user_print_detail.pop("printNumber")
|
||||||
|
user_print_detail.pop("serialId")
|
||||||
|
user_print_detail["printDate"] = datetime.strptime(
|
||||||
|
user_print_detail["printDate"], "%Y-%m-%d"
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the entry to the user print table with the random serialId
|
||||||
|
self.data.item.put_user_print_detail(user_id, serial_id, user_print_detail)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": serial_id,
|
||||||
|
"apiName": "CMUpsertUserPrintApi",
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_print_subtract_api_request(self, data: Dict) -> Dict:
|
||||||
|
upsert = data["userCardPrintState"]
|
||||||
|
user_id = data["userId"]
|
||||||
|
place_id = data["placeId"]
|
||||||
|
|
||||||
|
# save all user items
|
||||||
|
if "userItemList" in data:
|
||||||
|
for item in data["userItemList"]:
|
||||||
|
self.data.item.put_item(user_id, item)
|
||||||
|
|
||||||
|
# set the card print state to success and use the orderId as the key
|
||||||
|
self.data.item.put_user_print_state(
|
||||||
|
user_id,
|
||||||
|
id=upsert["orderId"],
|
||||||
|
hasCompleted=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"returnCode": "1", "apiName": "CMUpsertUserPrintSubtractApi"}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_print_cancel_api_request(self, data: Dict) -> Dict:
|
||||||
|
order_ids = data["orderIdList"]
|
||||||
|
user_id = data["userId"]
|
||||||
|
|
||||||
|
# set the card print state to success and use the orderId as the key
|
||||||
|
for order_id in order_ids:
|
||||||
|
self.data.item.put_user_print_state(
|
||||||
|
user_id,
|
||||||
|
id=order_id,
|
||||||
|
hasCompleted=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"returnCode": "1", "apiName": "CMUpsertUserPrintCancelApi"}
|
||||||
|
@ -30,3 +30,10 @@ class ChuniNewPlus(ChuniNew):
|
|||||||
"reflectorUri"
|
"reflectorUri"
|
||||||
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_data = super().handle_cm_get_user_preview_api_request(data)
|
||||||
|
|
||||||
|
# hardcode lastDataVersion for CardMaker 1.35
|
||||||
|
user_data["lastDataVersion"] = "2.05.00"
|
||||||
|
return user_data
|
||||||
|
@ -114,6 +114,76 @@ map_area = Table(
|
|||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gacha = Table(
|
||||||
|
"chuni_item_gacha",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("gachaId", Integer, nullable=False),
|
||||||
|
Column("totalGachaCnt", Integer, server_default="0"),
|
||||||
|
Column("ceilingGachaCnt", Integer, server_default="0"),
|
||||||
|
Column("dailyGachaCnt", Integer, server_default="0"),
|
||||||
|
Column("fiveGachaCnt", Integer, server_default="0"),
|
||||||
|
Column("elevenGachaCnt", Integer, server_default="0"),
|
||||||
|
Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||||
|
UniqueConstraint("user", "gachaId", name="chuni_item_gacha_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
print_state = Table(
|
||||||
|
"chuni_item_print_state",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("hasCompleted", Boolean, nullable=False, server_default="0"),
|
||||||
|
Column(
|
||||||
|
"limitDate", TIMESTAMP, nullable=False, server_default="2038-01-01 00:00:00.0"
|
||||||
|
),
|
||||||
|
Column("placeId", Integer),
|
||||||
|
Column("cardId", Integer),
|
||||||
|
Column("gachaId", Integer),
|
||||||
|
UniqueConstraint("id", "user", name="chuni_item_print_state_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
print_detail = Table(
|
||||||
|
"chuni_item_print_detail",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("cardId", Integer, nullable=False),
|
||||||
|
Column("printDate", TIMESTAMP, nullable=False),
|
||||||
|
Column("serialId", String(20), nullable=False),
|
||||||
|
Column("placeId", Integer, nullable=False),
|
||||||
|
Column("clientId", String(11), nullable=False),
|
||||||
|
Column("printerSerialId", String(20), nullable=False),
|
||||||
|
Column("printOption1", Boolean, server_default="0"),
|
||||||
|
Column("printOption2", Boolean, server_default="0"),
|
||||||
|
Column("printOption3", Boolean, server_default="0"),
|
||||||
|
Column("printOption4", Boolean, server_default="0"),
|
||||||
|
Column("printOption5", Boolean, server_default="0"),
|
||||||
|
Column("printOption6", Boolean, server_default="0"),
|
||||||
|
Column("printOption7", Boolean, server_default="0"),
|
||||||
|
Column("printOption8", Boolean, server_default="0"),
|
||||||
|
Column("printOption9", Boolean, server_default="0"),
|
||||||
|
Column("printOption10", Boolean, server_default="0"),
|
||||||
|
Column("created", String(255), server_default=""),
|
||||||
|
UniqueConstraint("serialId", name="chuni_item_print_detail_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniItemData(BaseData):
|
class ChuniItemData(BaseData):
|
||||||
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
|
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
|
||||||
@ -235,3 +305,89 @@ class ChuniItemData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
|
def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
|
||||||
|
sql = gacha.select(gacha.c.user == aime_id)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def put_user_gacha(
|
||||||
|
self, aime_id: int, gacha_id: int, gacha_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **gacha_data)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
user=aime_id, gachaId=gacha_id, **gacha_data
|
||||||
|
)
|
||||||
|
result = self.execute(conflict)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_user_print_states(
|
||||||
|
self, aime_id: int, has_completed: bool = False
|
||||||
|
) -> Optional[List[Row]]:
|
||||||
|
sql = print_state.select(
|
||||||
|
and_(
|
||||||
|
print_state.c.user == aime_id,
|
||||||
|
print_state.c.hasCompleted == has_completed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def get_user_print_states_by_gacha(
|
||||||
|
self, aime_id: int, gacha_id: int, has_completed: bool = False
|
||||||
|
) -> Optional[List[Row]]:
|
||||||
|
sql = print_state.select(
|
||||||
|
and_(
|
||||||
|
print_state.c.user == aime_id,
|
||||||
|
print_state.c.gachaId == gacha_id,
|
||||||
|
print_state.c.hasCompleted == has_completed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def put_user_print_state(self, aime_id: int, **print_data) -> Optional[int]:
|
||||||
|
sql = insert(print_state).values(user=aime_id, **print_data)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(user=aime_id, **print_data)
|
||||||
|
result = self.execute(conflict)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(
|
||||||
|
f"put_user_print_state: Failed to insert! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def put_user_print_detail(
|
||||||
|
self, aime_id: int, serial_id: str, user_print_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = insert(print_detail).values(
|
||||||
|
user=aime_id, serialId=serial_id, **user_print_data
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
user=aime_id, **user_print_data
|
||||||
|
)
|
||||||
|
result = self.execute(conflict)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(
|
||||||
|
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
@ -68,6 +68,60 @@ avatar = Table(
|
|||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gachas = Table(
|
||||||
|
"chuni_static_gachas",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column("version", Integer, nullable=False),
|
||||||
|
Column("gachaId", Integer, nullable=False),
|
||||||
|
Column("gachaName", String(255), nullable=False),
|
||||||
|
Column("type", Integer, nullable=False, server_default="0"),
|
||||||
|
Column("kind", Integer, nullable=False, server_default="0"),
|
||||||
|
Column("isCeiling", Boolean, server_default="0"),
|
||||||
|
Column("ceilingCnt", Integer, server_default="10"),
|
||||||
|
Column("changeRateCnt1", Integer, server_default="0"),
|
||||||
|
Column("changeRateCnt2", Integer, server_default="0"),
|
||||||
|
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||||
|
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||||
|
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||||
|
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||||
|
UniqueConstraint("version", "gachaId", "gachaName", name="chuni_static_gachas_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
cards = Table(
|
||||||
|
"chuni_static_cards",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column("version", Integer, nullable=False),
|
||||||
|
Column("cardId", Integer, nullable=False),
|
||||||
|
Column("charaName", String(255), nullable=False),
|
||||||
|
Column("charaId", Integer, nullable=False),
|
||||||
|
Column("presentName", String(255), nullable=False),
|
||||||
|
Column("rarity", Integer, server_default="2"),
|
||||||
|
Column("labelType", Integer, nullable=False),
|
||||||
|
Column("difType", Integer, nullable=False),
|
||||||
|
Column("miss", Integer, nullable=False),
|
||||||
|
Column("combo", Integer, nullable=False),
|
||||||
|
Column("chain", Integer, nullable=False),
|
||||||
|
Column("skillName", String(255), nullable=False),
|
||||||
|
UniqueConstraint("version", "cardId", name="chuni_static_cards_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
gacha_cards = Table(
|
||||||
|
"chuni_static_gacha_cards",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column("gachaId", Integer, nullable=False),
|
||||||
|
Column("cardId", Integer, nullable=False),
|
||||||
|
Column("rarity", Integer, nullable=False),
|
||||||
|
Column("weight", Integer, server_default="1"),
|
||||||
|
Column("isPickup", Boolean, server_default="0"),
|
||||||
|
UniqueConstraint("gachaId", "cardId", name="chuni_static_gacha_cards_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniStaticData(BaseData):
|
class ChuniStaticData(BaseData):
|
||||||
def put_event(
|
def put_event(
|
||||||
@ -265,3 +319,112 @@ class ChuniStaticData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
|
def put_gacha(
|
||||||
|
self,
|
||||||
|
version: int,
|
||||||
|
gacha_id: int,
|
||||||
|
gacha_name: int,
|
||||||
|
**gacha_data,
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = insert(gachas).values(
|
||||||
|
version=version,
|
||||||
|
gachaId=gacha_id,
|
||||||
|
gachaName=gacha_name,
|
||||||
|
**gacha_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
version=version,
|
||||||
|
gachaId=gacha_id,
|
||||||
|
gachaName=gacha_name,
|
||||||
|
**gacha_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_gachas(self, version: int) -> Optional[List[Dict]]:
|
||||||
|
sql = gachas.select(gachas.c.version <= version).order_by(
|
||||||
|
gachas.c.gachaId.asc()
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def get_gacha(self, version: int, gacha_id: int) -> Optional[Dict]:
|
||||||
|
sql = gachas.select(
|
||||||
|
and_(gachas.c.version <= version, gachas.c.gachaId == gacha_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchone()
|
||||||
|
|
||||||
|
def put_gacha_card(
|
||||||
|
self, gacha_id: int, card_id: int, **gacha_card
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = insert(gacha_cards).values(gachaId=gacha_id, cardId=card_id, **gacha_card)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
gachaId=gacha_id, cardId=card_id, **gacha_card
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_gacha_cards(self, gacha_id: int) -> Optional[List[Dict]]:
|
||||||
|
sql = gacha_cards.select(gacha_cards.c.gachaId == gacha_id)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def get_gacha_card_by_character(self, gacha_id: int, chara_id: int) -> Optional[Dict]:
|
||||||
|
sql_sub = (
|
||||||
|
select(cards.c.cardId)
|
||||||
|
.filter(
|
||||||
|
cards.c.charaId == chara_id
|
||||||
|
)
|
||||||
|
.scalar_subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Perform the main query, also rename the resulting column to ranking
|
||||||
|
sql = gacha_cards.select(and_(
|
||||||
|
gacha_cards.c.gachaId == gacha_id,
|
||||||
|
gacha_cards.c.cardId == sql_sub
|
||||||
|
))
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchone()
|
||||||
|
|
||||||
|
def put_card(self, version: int, card_id: int, **card_data) -> Optional[int]:
|
||||||
|
sql = insert(cards).values(version=version, cardId=card_id, **card_data)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(**card_data)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(f"Failed to insert card! card_id {card_id}")
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_card(self, version: int, card_id: int) -> Optional[Dict]:
|
||||||
|
sql = cards.select(and_(cards.c.version <= version, cards.c.cardId == card_id))
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchone()
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
from titles.cm.index import CardMakerServlet
|
from titles.cm.index import CardMakerServlet
|
||||||
from titles.cm.const import CardMakerConstants
|
from titles.cm.const import CardMakerConstants
|
||||||
from titles.cm.read import CardMakerReader
|
from titles.cm.read import CardMakerReader
|
||||||
|
from titles.cm.database import CardMakerData
|
||||||
|
|
||||||
index = CardMakerServlet
|
index = CardMakerServlet
|
||||||
reader = CardMakerReader
|
reader = CardMakerReader
|
||||||
|
database = CardMakerData
|
||||||
|
|
||||||
game_codes = [CardMakerConstants.GAME_CODE]
|
game_codes = [CardMakerConstants.GAME_CODE]
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ from titles.cm.const import CardMakerConstants
|
|||||||
from titles.cm.config import CardMakerConfig
|
from titles.cm.config import CardMakerConfig
|
||||||
|
|
||||||
|
|
||||||
class CardMaker136(CardMakerBase):
|
class CardMaker135(CardMakerBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
self.version = CardMakerConstants.VER_CARD_MAKER_136
|
self.version = CardMakerConstants.VER_CARD_MAKER_135
|
||||||
|
|
||||||
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
||||||
ret = super().handle_get_game_connect_api_request(data)
|
ret = super().handle_get_game_connect_api_request(data)
|
||||||
@ -32,7 +32,7 @@ class CardMaker136(CardMakerBase):
|
|||||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||||
ret = super().handle_get_game_setting_api_request(data)
|
ret = super().handle_get_game_setting_api_request(data)
|
||||||
ret["gameSetting"]["dataVersion"] = "1.35.00"
|
ret["gameSetting"]["dataVersion"] = "1.35.00"
|
||||||
ret["gameSetting"]["ongekiCmVersion"] = "1.35.04"
|
ret["gameSetting"]["ongekiCmVersion"] = "1.35.03"
|
||||||
ret["gameSetting"]["chuniCmVersion"] = "2.05.00"
|
ret["gameSetting"]["chuniCmVersion"] = "2.05.00"
|
||||||
ret["gameSetting"]["maimaiCmVersion"] = "1.25.00"
|
ret["gameSetting"]["maimaiCmVersion"] = "1.25.00"
|
||||||
return ret
|
return ret
|
@ -4,9 +4,9 @@ class CardMakerConstants:
|
|||||||
CONFIG_NAME = "cardmaker.yaml"
|
CONFIG_NAME = "cardmaker.yaml"
|
||||||
|
|
||||||
VER_CARD_MAKER = 0
|
VER_CARD_MAKER = 0
|
||||||
VER_CARD_MAKER_136 = 1
|
VER_CARD_MAKER_135 = 1
|
||||||
|
|
||||||
VERSION_NAMES = ("Card Maker 1.34", "Card Maker 1.36")
|
VERSION_NAMES = ("Card Maker 1.34", "Card Maker 1.35")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def game_ver_to_string(cls, ver: int):
|
def game_ver_to_string(cls, ver: int):
|
||||||
|
8
titles/cm/database.py
Normal file
8
titles/cm/database.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from core.data import Data
|
||||||
|
from core.config import CoreConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CardMakerData(Data):
|
||||||
|
def __init__(self, cfg: CoreConfig) -> None:
|
||||||
|
super().__init__(cfg)
|
||||||
|
# empty Card Maker database
|
@ -15,7 +15,7 @@ from core.config import CoreConfig
|
|||||||
from titles.cm.config import CardMakerConfig
|
from titles.cm.config import CardMakerConfig
|
||||||
from titles.cm.const import CardMakerConstants
|
from titles.cm.const import CardMakerConstants
|
||||||
from titles.cm.base import CardMakerBase
|
from titles.cm.base import CardMakerBase
|
||||||
from titles.cm.cm136 import CardMaker136
|
from titles.cm.cm135 import CardMaker135
|
||||||
|
|
||||||
|
|
||||||
class CardMakerServlet:
|
class CardMakerServlet:
|
||||||
@ -29,7 +29,7 @@ class CardMakerServlet:
|
|||||||
|
|
||||||
self.versions = [
|
self.versions = [
|
||||||
CardMakerBase(core_cfg, self.game_cfg),
|
CardMakerBase(core_cfg, self.game_cfg),
|
||||||
CardMaker136(core_cfg, self.game_cfg),
|
CardMaker135(core_cfg, self.game_cfg),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.logger = logging.getLogger("cardmaker")
|
self.logger = logging.getLogger("cardmaker")
|
||||||
@ -87,8 +87,8 @@ class CardMakerServlet:
|
|||||||
|
|
||||||
if version >= 130 and version < 135: # Card Maker
|
if version >= 130 and version < 135: # Card Maker
|
||||||
internal_ver = CardMakerConstants.VER_CARD_MAKER
|
internal_ver = CardMakerConstants.VER_CARD_MAKER
|
||||||
elif version >= 135 and version < 140: # Card Maker
|
elif version >= 135 and version < 136: # Card Maker 1.35
|
||||||
internal_ver = CardMakerConstants.VER_CARD_MAKER_136
|
internal_ver = CardMakerConstants.VER_CARD_MAKER_135
|
||||||
|
|
||||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||||
# If we get a 32 character long hex string, it's a hash and we're
|
# If we get a 32 character long hex string, it's a hash and we're
|
||||||
|
@ -12,6 +12,10 @@ from titles.ongeki.database import OngekiData
|
|||||||
from titles.cm.const import CardMakerConstants
|
from titles.cm.const import CardMakerConstants
|
||||||
from titles.ongeki.const import OngekiConstants
|
from titles.ongeki.const import OngekiConstants
|
||||||
from titles.ongeki.config import OngekiConfig
|
from titles.ongeki.config import OngekiConfig
|
||||||
|
from titles.mai2.database import Mai2Data
|
||||||
|
from titles.mai2.const import Mai2Constants
|
||||||
|
from titles.chuni.database import ChuniData
|
||||||
|
from titles.chuni.const import ChuniConstants
|
||||||
|
|
||||||
|
|
||||||
class CardMakerReader(BaseReader):
|
class CardMakerReader(BaseReader):
|
||||||
@ -25,6 +29,8 @@ class CardMakerReader(BaseReader):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(config, version, bin_dir, opt_dir, extra)
|
super().__init__(config, version, bin_dir, opt_dir, extra)
|
||||||
self.ongeki_data = OngekiData(config)
|
self.ongeki_data = OngekiData(config)
|
||||||
|
self.mai2_data = Mai2Data(config)
|
||||||
|
self.chuni_data = ChuniData(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
@ -34,15 +40,29 @@ class CardMakerReader(BaseReader):
|
|||||||
self.logger.error(f"Invalid Card Maker version {version}")
|
self.logger.error(f"Invalid Card Maker version {version}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
def _get_card_maker_directory(self, directory: str) -> str:
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
for dir in dirs:
|
||||||
|
if (
|
||||||
|
os.path.exists(f"{root}/{dir}/MU3")
|
||||||
|
and os.path.exists(f"{root}/{dir}/MAI")
|
||||||
|
and os.path.exists(f"{root}/{dir}/CHU")
|
||||||
|
):
|
||||||
|
return f"{root}/{dir}"
|
||||||
|
|
||||||
def read(self) -> None:
|
def read(self) -> None:
|
||||||
static_datas = {
|
static_datas = {
|
||||||
"static_gachas.csv": "read_ongeki_gacha_csv",
|
"static_gachas.csv": "read_ongeki_gacha_csv",
|
||||||
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv",
|
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv",
|
||||||
}
|
}
|
||||||
|
|
||||||
data_dirs = []
|
|
||||||
|
|
||||||
if self.bin_dir is not None:
|
if self.bin_dir is not None:
|
||||||
|
data_dir = self._get_card_maker_directory(self.bin_dir)
|
||||||
|
|
||||||
|
self.read_chuni_card(f"{data_dir}/CHU/Data/A000/card")
|
||||||
|
self.read_chuni_gacha(f"{data_dir}/CHU/Data/A000/gacha")
|
||||||
|
|
||||||
|
self.read_mai2_card(f"{data_dir}/MAI/Data/A000/card")
|
||||||
for file, func in static_datas.items():
|
for file, func in static_datas.items():
|
||||||
if os.path.exists(f"{self.bin_dir}/MU3/{file}"):
|
if os.path.exists(f"{self.bin_dir}/MU3/{file}"):
|
||||||
read_csv = getattr(CardMakerReader, func)
|
read_csv = getattr(CardMakerReader, func)
|
||||||
@ -53,13 +73,163 @@ class CardMakerReader(BaseReader):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.opt_dir is not None:
|
if self.opt_dir is not None:
|
||||||
data_dirs += self.get_data_directories(self.opt_dir)
|
data_dirs = self.get_data_directories(self.opt_dir)
|
||||||
|
|
||||||
# ONGEKI (MU3) cnnot easily access the bin data(A000.pac)
|
# ONGEKI (MU3) cnnot easily access the bin data(A000.pac)
|
||||||
# so only opt_dir will work for now
|
# so only opt_dir will work for now
|
||||||
for dir in data_dirs:
|
for dir in data_dirs:
|
||||||
|
self.read_chuni_card(f"{dir}/CHU/card")
|
||||||
|
self.read_chuni_gacha(f"{dir}/CHU/gacha")
|
||||||
|
|
||||||
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
|
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
|
||||||
|
|
||||||
|
def read_chuni_card(self, base_dir: str) -> None:
|
||||||
|
self.logger.info(f"Reading cards from {base_dir}...")
|
||||||
|
|
||||||
|
version_ids = {
|
||||||
|
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
||||||
|
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
||||||
|
# Chunithm SUN, ignore for now
|
||||||
|
"v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(base_dir):
|
||||||
|
for dir in dirs:
|
||||||
|
if os.path.exists(f"{root}/{dir}/Card.xml"):
|
||||||
|
with open(f"{root}/{dir}/Card.xml", "r", encoding="utf-8") as f:
|
||||||
|
troot = ET.fromstring(f.read())
|
||||||
|
|
||||||
|
card_id = int(troot.find("name").find("id").text)
|
||||||
|
|
||||||
|
chara_name = troot.find("chuniCharaName").find("str").text
|
||||||
|
chara_id = troot.find("chuniCharaName").find("id").text
|
||||||
|
version = version_ids[
|
||||||
|
troot.find("netOpenName").find("str").text[:5]
|
||||||
|
]
|
||||||
|
present_name = troot.find("chuniPresentName").find("str").text
|
||||||
|
rarity = int(troot.find("rareType").text)
|
||||||
|
label = int(troot.find("labelType").text)
|
||||||
|
dif = int(troot.find("difType").text)
|
||||||
|
miss = int(troot.find("miss").text)
|
||||||
|
combo = int(troot.find("combo").text)
|
||||||
|
chain = int(troot.find("chain").text)
|
||||||
|
skill_name = troot.find("skillName").text
|
||||||
|
|
||||||
|
self.chuni_data.static.put_card(
|
||||||
|
version,
|
||||||
|
card_id,
|
||||||
|
charaName=chara_name,
|
||||||
|
charaId=chara_id,
|
||||||
|
presentName=present_name,
|
||||||
|
rarity=rarity,
|
||||||
|
labelType=label,
|
||||||
|
difType=dif,
|
||||||
|
miss=miss,
|
||||||
|
combo=combo,
|
||||||
|
chain=chain,
|
||||||
|
skillName=skill_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(f"Added chuni card {card_id}")
|
||||||
|
|
||||||
|
def read_chuni_gacha(self, base_dir: str) -> None:
|
||||||
|
self.logger.info(f"Reading gachas from {base_dir}...")
|
||||||
|
|
||||||
|
version_ids = {
|
||||||
|
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
||||||
|
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
||||||
|
# Chunithm SUN, ignore for now
|
||||||
|
"v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(base_dir):
|
||||||
|
for dir in dirs:
|
||||||
|
if os.path.exists(f"{root}/{dir}/Gacha.xml"):
|
||||||
|
with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f:
|
||||||
|
troot = ET.fromstring(f.read())
|
||||||
|
|
||||||
|
name = troot.find("gachaName").text
|
||||||
|
gacha_id = int(troot.find("name").find("id").text)
|
||||||
|
|
||||||
|
version = version_ids[
|
||||||
|
troot.find("netOpenName").find("str").text[:5]
|
||||||
|
]
|
||||||
|
ceiling_cnt = int(troot.find("ceilingNum").text)
|
||||||
|
gacha_type = int(troot.find("gachaType").text)
|
||||||
|
is_ceiling = (
|
||||||
|
True if troot.find("ceilingType").text == "1" else False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.chuni_data.static.put_gacha(
|
||||||
|
version,
|
||||||
|
gacha_id,
|
||||||
|
name,
|
||||||
|
type=gacha_type,
|
||||||
|
isCeiling=is_ceiling,
|
||||||
|
ceilingCnt=ceiling_cnt,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(f"Added chuni gacha {gacha_id}")
|
||||||
|
|
||||||
|
for gacha_card in troot.find("infos").iter("GachaCardDataInfo"):
|
||||||
|
# get the card ID from the id element
|
||||||
|
card_id = gacha_card.find("cardName").find("id").text
|
||||||
|
|
||||||
|
# get the weight from the weight element
|
||||||
|
weight = int(gacha_card.find("weight").text)
|
||||||
|
|
||||||
|
# get the pickup flag from the pickup element
|
||||||
|
is_pickup = (
|
||||||
|
True if gacha_card.find("pickup").text == "1" else False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.chuni_data.static.put_gacha_card(
|
||||||
|
gacha_id,
|
||||||
|
card_id,
|
||||||
|
weight=weight,
|
||||||
|
rarity=2,
|
||||||
|
isPickup=is_pickup,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"Added chuni card {card_id} to gacha {gacha_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_mai2_card(self, base_dir: str) -> None:
|
||||||
|
self.logger.info(f"Reading cards from {base_dir}...")
|
||||||
|
|
||||||
|
version_ids = {
|
||||||
|
"1.00": Mai2Constants.VER_MAIMAI_DX,
|
||||||
|
"1.05": Mai2Constants.VER_MAIMAI_DX_PLUS,
|
||||||
|
"1.09": Mai2Constants.VER_MAIMAI_DX_PLUS,
|
||||||
|
"1.10": Mai2Constants.VER_MAIMAI_DX_SPLASH,
|
||||||
|
"1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS,
|
||||||
|
"1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE,
|
||||||
|
"1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS,
|
||||||
|
}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(base_dir):
|
||||||
|
for dir in dirs:
|
||||||
|
if os.path.exists(f"{root}/{dir}/Card.xml"):
|
||||||
|
with open(f"{root}/{dir}/Card.xml", "r", encoding="utf-8") as f:
|
||||||
|
troot = ET.fromstring(f.read())
|
||||||
|
|
||||||
|
name = troot.find("name").find("str").text
|
||||||
|
card_id = int(troot.find("name").find("id").text)
|
||||||
|
|
||||||
|
version = version_ids[
|
||||||
|
troot.find("enableVersion").find("str").text
|
||||||
|
]
|
||||||
|
|
||||||
|
enabled = (
|
||||||
|
True if troot.find("disable").text == "false" else False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mai2_data.static.put_card(
|
||||||
|
version, card_id, name, enabled=enabled
|
||||||
|
)
|
||||||
|
self.logger.info(f"Added mai2 card {card_id}")
|
||||||
|
|
||||||
def read_ongeki_gacha_csv(self, file_path: str) -> None:
|
def read_ongeki_gacha_csv(self, file_path: str) -> None:
|
||||||
self.logger.info(f"Reading gachas from {file_path}...")
|
self.logger.info(f"Reading gachas from {file_path}...")
|
||||||
|
|
||||||
@ -76,7 +246,7 @@ class CardMakerReader(BaseReader):
|
|||||||
maxSelectPoint=row["maxSelectPoint"],
|
maxSelectPoint=row["maxSelectPoint"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.info(f"Added gacha {row['gachaId']}")
|
self.logger.info(f"Added ongeki gacha {row['gachaId']}")
|
||||||
|
|
||||||
def read_ongeki_gacha_card_csv(self, file_path: str) -> None:
|
def read_ongeki_gacha_card_csv(self, file_path: str) -> None:
|
||||||
self.logger.info(f"Reading gacha cards from {file_path}...")
|
self.logger.info(f"Reading gacha cards from {file_path}...")
|
||||||
@ -93,7 +263,7 @@ class CardMakerReader(BaseReader):
|
|||||||
isSelect=True if row["isSelect"] == "1" else False,
|
isSelect=True if row["isSelect"] == "1" else False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.info(f"Added card {row['cardId']} to gacha")
|
self.logger.info(f"Added ongeki card {row['cardId']} to gacha")
|
||||||
|
|
||||||
def read_ongeki_gacha(self, base_dir: str) -> None:
|
def read_ongeki_gacha(self, base_dir: str) -> None:
|
||||||
self.logger.info(f"Reading gachas from {base_dir}...")
|
self.logger.info(f"Reading gachas from {base_dir}...")
|
||||||
@ -152,4 +322,4 @@ class CardMakerReader(BaseReader):
|
|||||||
isCeiling=is_ceiling,
|
isCeiling=is_ceiling,
|
||||||
maxSelectPoint=max_select_point,
|
maxSelectPoint=max_select_point,
|
||||||
)
|
)
|
||||||
self.logger.info(f"Added gacha {gacha_id}")
|
self.logger.info(f"Added ongeki gacha {gacha_id}")
|
||||||
|
1
titles/cm/schema/__init__.py
Normal file
1
titles/cm/schema/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__all__ = []
|
@ -7,4 +7,4 @@ index = Mai2Servlet
|
|||||||
database = Mai2Data
|
database = Mai2Data
|
||||||
reader = Mai2Reader
|
reader = Mai2Reader
|
||||||
game_codes = [Mai2Constants.GAME_CODE]
|
game_codes = [Mai2Constants.GAME_CODE]
|
||||||
current_schema_version = 2
|
current_schema_version = 3
|
||||||
|
@ -202,6 +202,16 @@ class Mai2Base:
|
|||||||
for act in v:
|
for act in v:
|
||||||
self.data.profile.put_profile_activity(user_id, act)
|
self.data.profile.put_profile_activity(user_id, act)
|
||||||
|
|
||||||
|
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||||
|
for charge in upsert["userChargeList"]:
|
||||||
|
self.data.item.put_charge(
|
||||||
|
user_id,
|
||||||
|
charge["chargeId"],
|
||||||
|
charge["stock"],
|
||||||
|
datetime.strptime(charge["purchaseDate"], "%Y-%m-%d %H:%M:%S"),
|
||||||
|
datetime.strptime(charge["validDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
)
|
||||||
|
|
||||||
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
|
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
|
||||||
for char in upsert["userCharacterList"]:
|
for char in upsert["userCharacterList"]:
|
||||||
self.data.item.put_character(
|
self.data.item.put_character(
|
||||||
@ -299,10 +309,67 @@ class Mai2Base:
|
|||||||
return {"userId": data["userId"], "userOption": options_dict}
|
return {"userId": data["userId"], "userOption": options_dict}
|
||||||
|
|
||||||
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
|
||||||
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
|
user_cards = self.data.item.get_cards(data["userId"])
|
||||||
|
if user_cards is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userCardList": []
|
||||||
|
}
|
||||||
|
|
||||||
|
max_ct = data["maxCount"]
|
||||||
|
next_idx = data["nextIndex"]
|
||||||
|
start_idx = next_idx
|
||||||
|
end_idx = max_ct + start_idx
|
||||||
|
|
||||||
|
if len(user_cards[start_idx:]) > max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
card_list = []
|
||||||
|
for card in user_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["startDate"] = datetime.strftime(
|
||||||
|
tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(
|
||||||
|
tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
card_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userCardList": card_list[start_idx:end_idx]
|
||||||
|
}
|
||||||
|
|
||||||
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
||||||
return {"userId": data["userId"], "length": 0, "userChargeList": []}
|
user_charges = self.data.item.get_charges(data["userId"])
|
||||||
|
if user_charges is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": 0,
|
||||||
|
"userChargeList": []
|
||||||
|
}
|
||||||
|
|
||||||
|
user_charge_list = []
|
||||||
|
for charge in user_charges:
|
||||||
|
tmp = charge._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["purchaseDate"] = datetime.strftime(
|
||||||
|
tmp["purchaseDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["validDate"] = datetime.strftime(
|
||||||
|
tmp["validDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
user_charge_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": len(user_charge_list),
|
||||||
|
"userChargeList": user_charge_list
|
||||||
|
}
|
||||||
|
|
||||||
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
kind = int(data["nextIndex"] / 10000000000)
|
kind = int(data["nextIndex"] / 10000000000)
|
||||||
@ -313,15 +380,13 @@ class Mai2Base:
|
|||||||
|
|
||||||
for x in range(next_idx, data["maxCount"]):
|
for x in range(next_idx, data["maxCount"]):
|
||||||
try:
|
try:
|
||||||
user_item_list.append(
|
user_item_list.append({
|
||||||
{
|
"itemKind": user_items[x]["itemKind"],
|
||||||
"item_kind": user_items[x]["item_kind"],
|
"itemId": user_items[x]["itemId"],
|
||||||
"item_id": user_items[x]["item_id"],
|
|
||||||
"stock": user_items[x]["stock"],
|
"stock": user_items[x]["stock"],
|
||||||
"isValid": user_items[x]["is_valid"],
|
"isValid": user_items[x]["isValid"]
|
||||||
}
|
})
|
||||||
)
|
except IndexError:
|
||||||
except:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if len(user_item_list) == data["maxCount"]:
|
if len(user_item_list) == data["maxCount"]:
|
||||||
@ -332,21 +397,18 @@ class Mai2Base:
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"nextIndex": next_idx,
|
"nextIndex": next_idx,
|
||||||
"itemKind": kind,
|
"itemKind": kind,
|
||||||
"userItemList": user_item_list,
|
"userItemList": user_item_list
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
characters = self.data.item.get_characters(data["userId"])
|
characters = self.data.item.get_characters(data["userId"])
|
||||||
|
|
||||||
chara_list = []
|
chara_list = []
|
||||||
for chara in characters:
|
for chara in characters:
|
||||||
chara_list.append(
|
tmp = chara._asdict()
|
||||||
{
|
tmp.pop("id")
|
||||||
"characterId": chara["character_id"],
|
tmp.pop("user")
|
||||||
"level": chara["level"],
|
chara_list.append(tmp)
|
||||||
"awakening": chara["awakening"],
|
|
||||||
"useCount": chara["use_count"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"userId": data["userId"], "userCharacterList": chara_list}
|
return {"userId": data["userId"], "userCharacterList": chara_list}
|
||||||
|
|
||||||
@ -417,10 +479,21 @@ class Mai2Base:
|
|||||||
tmp.pop("user")
|
tmp.pop("user")
|
||||||
mlst.append(tmp)
|
mlst.append(tmp)
|
||||||
|
|
||||||
return {"userActivity": {"playList": plst, "musicList": mlst}}
|
return {
|
||||||
|
"userActivity": {
|
||||||
|
"playList": plst,
|
||||||
|
"musicList": mlst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
||||||
user_courses = self.data.score.get_courses(data["userId"])
|
user_courses = self.data.score.get_courses(data["userId"])
|
||||||
|
if user_courses is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userCourseList": []
|
||||||
|
}
|
||||||
|
|
||||||
course_list = []
|
course_list = []
|
||||||
for course in user_courses:
|
for course in user_courses:
|
||||||
@ -429,7 +502,11 @@ class Mai2Base:
|
|||||||
tmp.pop("id")
|
tmp.pop("id")
|
||||||
course_list.append(tmp)
|
course_list.append(tmp)
|
||||||
|
|
||||||
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userCourseList": course_list
|
||||||
|
}
|
||||||
|
|
||||||
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
|
||||||
# No support for custom pfps
|
# No support for custom pfps
|
||||||
@ -540,18 +617,11 @@ class Mai2Base:
|
|||||||
|
|
||||||
if songs is not None:
|
if songs is not None:
|
||||||
for song in songs:
|
for song in songs:
|
||||||
music_detail_list.append(
|
tmp = song._asdict()
|
||||||
{
|
tmp.pop("id")
|
||||||
"musicId": song["song_id"],
|
tmp.pop("user")
|
||||||
"level": song["chart_id"],
|
music_detail_list.append(tmp)
|
||||||
"playCount": song["play_count"],
|
|
||||||
"achievement": song["achievement"],
|
|
||||||
"comboStatus": song["combo_status"],
|
|
||||||
"syncStatus": song["sync_status"],
|
|
||||||
"deluxscoreMax": song["dx_score"],
|
|
||||||
"scoreRank": song["score_rank"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if len(music_detail_list) == data["maxCount"]:
|
if len(music_detail_list) == data["maxCount"]:
|
||||||
next_index = data["maxCount"] + data["nextIndex"]
|
next_index = data["maxCount"] + data["nextIndex"]
|
||||||
break
|
break
|
||||||
@ -559,5 +629,5 @@ class Mai2Base:
|
|||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"nextIndex": next_index,
|
"nextIndex": next_index,
|
||||||
"userMusicList": [{"userMusicDetailList": music_detail_list}],
|
"userMusicList": [{"userMusicDetailList": music_detail_list}]
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ class Mai2Servlet:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
if url_path.lower() == "/ping":
|
if url_path.lower() == "ping":
|
||||||
return zlib.compress(b'{"returnCode": "1"}')
|
return zlib.compress(b'{"returnCode": "1"}')
|
||||||
|
|
||||||
req_raw = request.content.getvalue()
|
req_raw = request.content.getvalue()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from core.data.schema import BaseData, metadata
|
from core.data.schema import BaseData, metadata
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional, Dict, List
|
from typing import Optional, Dict, List
|
||||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||||
@ -17,11 +18,11 @@ character = Table(
|
|||||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
),
|
),
|
||||||
Column("character_id", Integer, nullable=False),
|
Column("characterId", Integer, nullable=False),
|
||||||
Column("level", Integer, nullable=False, server_default="1"),
|
Column("level", Integer, nullable=False, server_default="1"),
|
||||||
Column("awakening", Integer, nullable=False, server_default="0"),
|
Column("awakening", Integer, nullable=False, server_default="0"),
|
||||||
Column("use_count", Integer, nullable=False, server_default="0"),
|
Column("useCount", Integer, nullable=False, server_default="0"),
|
||||||
UniqueConstraint("user", "character_id", name="mai2_item_character_uk"),
|
UniqueConstraint("user", "characterId", name="mai2_item_character_uk"),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,13 +35,13 @@ card = Table(
|
|||||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
),
|
),
|
||||||
Column("card_kind", Integer, nullable=False),
|
Column("cardId", Integer, nullable=False),
|
||||||
Column("card_id", Integer, nullable=False),
|
Column("cardTypeId", Integer, nullable=False),
|
||||||
Column("chara_id", Integer, nullable=False),
|
Column("charaId", Integer, nullable=False),
|
||||||
Column("map_id", Integer, nullable=False),
|
Column("mapId", Integer, nullable=False),
|
||||||
Column("start_date", String(255), nullable=False),
|
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||||
Column("end_date", String(255), nullable=False),
|
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||||
UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"),
|
UniqueConstraint("user", "cardId", "cardTypeId", name="mai2_item_card_uk"),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,11 +54,11 @@ item = Table(
|
|||||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
),
|
),
|
||||||
Column("item_kind", Integer, nullable=False),
|
Column("itemId", Integer, nullable=False),
|
||||||
Column("item_id", Integer, nullable=False),
|
Column("itemKind", Integer, nullable=False),
|
||||||
Column("stock", Integer, nullable=False, server_default="1"),
|
Column("stock", Integer, nullable=False, server_default="1"),
|
||||||
Column("is_valid", Boolean, nullable=False, server_default="1"),
|
Column("isValid", Boolean, nullable=False, server_default="1"),
|
||||||
UniqueConstraint("user", "item_kind", "item_id", name="mai2_item_item_uk"),
|
UniqueConstraint("user", "itemId", "itemKind", name="mai2_item_item_uk"),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -139,11 +140,44 @@ charge = Table(
|
|||||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
),
|
),
|
||||||
Column("charge_id", Integer, nullable=False),
|
Column("chargeId", Integer, nullable=False),
|
||||||
Column("stock", Integer, nullable=False),
|
Column("stock", Integer, nullable=False),
|
||||||
Column("purchase_date", String(255), nullable=False),
|
Column("purchaseDate", String(255), nullable=False),
|
||||||
Column("valid_date", String(255), nullable=False),
|
Column("validDate", String(255), nullable=False),
|
||||||
UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"),
|
UniqueConstraint("user", "chargeId", name="mai2_item_charge_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
print_detail = Table(
|
||||||
|
"mai2_item_print_detail",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("orderId", Integer),
|
||||||
|
Column("printNumber", Integer),
|
||||||
|
Column("printDate", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||||
|
Column("serialId", String(20), nullable=False),
|
||||||
|
Column("placeId", Integer, nullable=False),
|
||||||
|
Column("clientId", String(11), nullable=False),
|
||||||
|
Column("printerSerialId", String(20), nullable=False),
|
||||||
|
Column("cardRomVersion", Integer),
|
||||||
|
Column("isHolograph", Boolean, server_default="1"),
|
||||||
|
Column("printOption1", Boolean, server_default="0"),
|
||||||
|
Column("printOption2", Boolean, server_default="0"),
|
||||||
|
Column("printOption3", Boolean, server_default="0"),
|
||||||
|
Column("printOption4", Boolean, server_default="0"),
|
||||||
|
Column("printOption5", Boolean, server_default="0"),
|
||||||
|
Column("printOption6", Boolean, server_default="0"),
|
||||||
|
Column("printOption7", Boolean, server_default="0"),
|
||||||
|
Column("printOption8", Boolean, server_default="0"),
|
||||||
|
Column("printOption9", Boolean, server_default="0"),
|
||||||
|
Column("printOption10", Boolean, server_default="0"),
|
||||||
|
Column("created", String(255), server_default=""),
|
||||||
|
UniqueConstraint("user", "serialId", name="mai2_item_print_detail_uk"),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -154,15 +188,15 @@ class Mai2ItemData(BaseData):
|
|||||||
) -> None:
|
) -> None:
|
||||||
sql = insert(item).values(
|
sql = insert(item).values(
|
||||||
user=user_id,
|
user=user_id,
|
||||||
item_kind=item_kind,
|
itemKind=item_kind,
|
||||||
item_id=item_id,
|
itemId=item_id,
|
||||||
stock=stock,
|
stock=stock,
|
||||||
is_valid=is_valid,
|
isValid=is_valid,
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
stock=stock,
|
stock=stock,
|
||||||
is_valid=is_valid,
|
isValid=is_valid,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
@ -178,7 +212,7 @@ class Mai2ItemData(BaseData):
|
|||||||
sql = item.select(item.c.user == user_id)
|
sql = item.select(item.c.user == user_id)
|
||||||
else:
|
else:
|
||||||
sql = item.select(
|
sql = item.select(
|
||||||
and_(item.c.user == user_id, item.c.item_kind == item_kind)
|
and_(item.c.user == user_id, item.c.itemKind == item_kind)
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
@ -190,8 +224,8 @@ class Mai2ItemData(BaseData):
|
|||||||
sql = item.select(
|
sql = item.select(
|
||||||
and_(
|
and_(
|
||||||
item.c.user == user_id,
|
item.c.user == user_id,
|
||||||
item.c.item_kind == item_kind,
|
item.c.itemKind == item_kind,
|
||||||
item.c.item_id == item_id,
|
item.c.itemId == item_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -382,3 +416,93 @@ class Mai2ItemData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
|
def put_card(
|
||||||
|
self,
|
||||||
|
user_id: int,
|
||||||
|
card_type_id: int,
|
||||||
|
card_kind: int,
|
||||||
|
chara_id: int,
|
||||||
|
map_id: int,
|
||||||
|
) -> Optional[Row]:
|
||||||
|
sql = insert(card).values(
|
||||||
|
user=user_id,
|
||||||
|
cardId=card_type_id,
|
||||||
|
cardTypeId=card_kind,
|
||||||
|
charaId=chara_id,
|
||||||
|
mapId=map_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(charaId=chara_id, mapId=map_id)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(
|
||||||
|
f"put_card: failed to insert card! user_id: {user_id}, kind: {kind}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_cards(self, user_id: int, kind: int = None) -> Optional[Row]:
|
||||||
|
if kind is None:
|
||||||
|
sql = card.select(card.c.user == user_id)
|
||||||
|
else:
|
||||||
|
sql = card.select(and_(card.c.user == user_id, card.c.cardKind == kind))
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def put_charge(
|
||||||
|
self,
|
||||||
|
user_id: int,
|
||||||
|
charge_id: int,
|
||||||
|
stock: int,
|
||||||
|
purchase_date: datetime,
|
||||||
|
valid_date: datetime,
|
||||||
|
) -> Optional[Row]:
|
||||||
|
sql = insert(charge).values(
|
||||||
|
user=user_id,
|
||||||
|
chargeId=charge_id,
|
||||||
|
stock=stock,
|
||||||
|
purchaseDate=purchase_date,
|
||||||
|
validDate=valid_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
stock=stock, purchaseDate=purchase_date, validDate=valid_date
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(
|
||||||
|
f"put_card: failed to insert charge! user_id: {user_id}, chargeId: {charge_id}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_charges(self, user_id: int) -> Optional[Row]:
|
||||||
|
sql = charge.select(charge.c.user == user_id)
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def put_user_print_detail(
|
||||||
|
self, aime_id: int, serial_id: str, user_print_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = insert(print_detail).values(
|
||||||
|
user=aime_id, serialId=serial_id, **user_print_data
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(**user_print_data)
|
||||||
|
result = self.execute(conflict)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(
|
||||||
|
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
@ -448,7 +448,9 @@ class Mai2ProfileData(BaseData):
|
|||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]:
|
def get_profile_activity(
|
||||||
|
self, user_id: int, kind: int = None
|
||||||
|
) -> Optional[List[Row]]:
|
||||||
sql = activity.select(
|
sql = activity.select(
|
||||||
and_(
|
and_(
|
||||||
activity.c.user == user_id,
|
activity.c.user == user_id,
|
||||||
@ -459,4 +461,4 @@ class Mai2ProfileData(BaseData):
|
|||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchone()
|
return result.fetchall()
|
||||||
|
@ -242,7 +242,7 @@ class Mai2ScoreData(BaseData):
|
|||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_courses(self, user_id: int) -> Optional[List[Row]]:
|
def get_courses(self, user_id: int) -> Optional[List[Row]]:
|
||||||
sql = course.select(best_score.c.user == user_id)
|
sql = course.select(course.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -53,6 +53,22 @@ ticket = Table(
|
|||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cards = Table(
|
||||||
|
"mai2_static_cards",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column("version", Integer, nullable=False),
|
||||||
|
Column("cardId", Integer, nullable=False),
|
||||||
|
Column("cardName", String(255), nullable=False),
|
||||||
|
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||||
|
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||||
|
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||||
|
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||||
|
Column("enabled", Boolean, server_default="1"),
|
||||||
|
UniqueConstraint("version", "cardId", "cardName", name="mai2_static_cards_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Mai2StaticData(BaseData):
|
class Mai2StaticData(BaseData):
|
||||||
def put_game_event(
|
def put_game_event(
|
||||||
@ -166,6 +182,8 @@ class Mai2StaticData(BaseData):
|
|||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(price=ticket_price)
|
conflict = sql.on_duplicate_key_update(price=ticket_price)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(price=ticket_price)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
||||||
@ -208,3 +226,24 @@ class Mai2StaticData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
|
def put_card(self, version: int, card_id: int, card_name: str, **card_data) -> int:
|
||||||
|
sql = insert(cards).values(
|
||||||
|
version=version, cardId=card_id, cardName=card_name, **card_data
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_update(**card_data)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warn(f"Failed to insert card {card_id}")
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def get_enabled_cards(self, version: int) -> Optional[List[Row]]:
|
||||||
|
sql = cards.select(and_(cards.c.version == version, cards.c.enabled == True))
|
||||||
|
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import Any, List, Dict
|
from typing import Any, List, Dict
|
||||||
|
from random import randint
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import pytz
|
import pytz
|
||||||
import json
|
import json
|
||||||
@ -13,3 +14,176 @@ class Mai2Universe(Mai2Base):
|
|||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
|
|
||||||
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userName": p["userName"],
|
||||||
|
"rating": p["playerRating"],
|
||||||
|
# hardcode lastDataVersion for CardMaker 1.34
|
||||||
|
"lastDataVersion": "1.20.00",
|
||||||
|
"isLogin": False,
|
||||||
|
"isExistSellingCard": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
# user already exists, because the preview checks that already
|
||||||
|
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
|
||||||
|
cards = self.data.card.get_user_cards(data["userId"])
|
||||||
|
if cards is None or len(cards) == 0:
|
||||||
|
# This should never happen
|
||||||
|
self.logger.error(
|
||||||
|
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# get the dict representation of the row so we can modify values
|
||||||
|
user_data = p._asdict()
|
||||||
|
|
||||||
|
# remove the values the game doesn't want
|
||||||
|
user_data.pop("id")
|
||||||
|
user_data.pop("user")
|
||||||
|
user_data.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userData": user_data}
|
||||||
|
|
||||||
|
def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
selling_cards = self.data.static.get_enabled_cards(self.version)
|
||||||
|
if selling_cards is None:
|
||||||
|
return {"length": 0, "sellingCardList": []}
|
||||||
|
|
||||||
|
selling_card_list = []
|
||||||
|
for card in selling_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("version")
|
||||||
|
tmp.pop("cardName")
|
||||||
|
tmp.pop("enabled")
|
||||||
|
|
||||||
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["noticeStartDate"] = datetime.strftime(
|
||||||
|
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
tmp["noticeEndDate"] = datetime.strftime(
|
||||||
|
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
selling_card_list.append(tmp)
|
||||||
|
|
||||||
|
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
|
||||||
|
|
||||||
|
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_cards = self.data.item.get_cards(data["userId"])
|
||||||
|
if user_cards is None:
|
||||||
|
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
|
||||||
|
|
||||||
|
max_ct = data["maxCount"]
|
||||||
|
next_idx = data["nextIndex"]
|
||||||
|
start_idx = next_idx
|
||||||
|
end_idx = max_ct + start_idx
|
||||||
|
|
||||||
|
if len(user_cards[start_idx:]) > max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
card_list = []
|
||||||
|
for card in user_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
|
||||||
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
card_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"length": len(card_list[start_idx:end_idx]),
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userCardList": card_list[start_idx:end_idx],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
super().handle_get_user_item_api_request(data)
|
||||||
|
|
||||||
|
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
|
characters = self.data.item.get_characters(data["userId"])
|
||||||
|
|
||||||
|
chara_list = []
|
||||||
|
for chara in characters:
|
||||||
|
chara_list.append(
|
||||||
|
{
|
||||||
|
"characterId": chara["characterId"],
|
||||||
|
# no clue why those values are even needed
|
||||||
|
"point": 0,
|
||||||
|
"count": 0,
|
||||||
|
"level": chara["level"],
|
||||||
|
"nextAwake": 0,
|
||||||
|
"nextAwakePercent": 0,
|
||||||
|
"favorite": False,
|
||||||
|
"awakening": chara["awakening"],
|
||||||
|
"useCount": chara["useCount"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"length": len(chara_list),
|
||||||
|
"userCharacterList": chara_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"length": 0, "userPrintDetailList": []}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
upsert = data["userPrintDetail"]
|
||||||
|
|
||||||
|
# set a random card serial number
|
||||||
|
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
||||||
|
|
||||||
|
user_card = upsert["userCard"]
|
||||||
|
self.data.item.put_card(
|
||||||
|
user_id,
|
||||||
|
user_card["cardId"],
|
||||||
|
user_card["cardTypeId"],
|
||||||
|
user_card["charaId"],
|
||||||
|
user_card["mapId"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# properly format userPrintDetail for the database
|
||||||
|
upsert.pop("userCard")
|
||||||
|
upsert.pop("serialId")
|
||||||
|
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": serial_id,
|
||||||
|
"startDate": "2018-01-01 00:00:00",
|
||||||
|
"endDate": "2038-01-01 00:00:00",
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": data["userPrintlog"]["serialId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
@ -4,12 +4,19 @@ import pytz
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.universe import Mai2Universe
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2UniversePlus(Mai2Base):
|
class Mai2UniversePlus(Mai2Universe):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||||
|
|
||||||
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_data = super().handle_cm_get_user_preview_api_request(data)
|
||||||
|
|
||||||
|
# hardcode lastDataVersion for CardMaker 1.35
|
||||||
|
user_data["lastDataVersion"] = "1.25.00"
|
||||||
|
return user_data
|
||||||
|
@ -452,7 +452,8 @@ class OngekiBase:
|
|||||||
tmp.pop("id")
|
tmp.pop("id")
|
||||||
items.append(tmp)
|
items.append(tmp)
|
||||||
|
|
||||||
xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items)
|
xout = kind * 10000000000 + \
|
||||||
|
(data["nextIndex"] % 10000000000) + len(items)
|
||||||
|
|
||||||
if len(items) < data["maxCount"] or data["maxCount"] == 0:
|
if len(items) < data["maxCount"] or data["maxCount"] == 0:
|
||||||
nextIndex = 0
|
nextIndex = 0
|
||||||
@ -851,7 +852,8 @@ class OngekiBase:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||||
self.data.profile.put_profile_options(user_id, upsert["userOption"][0])
|
self.data.profile.put_profile_options(
|
||||||
|
user_id, upsert["userOption"][0])
|
||||||
|
|
||||||
if "userPlaylogList" in upsert:
|
if "userPlaylogList" in upsert:
|
||||||
for playlog in upsert["userPlaylogList"]:
|
for playlog in upsert["userPlaylogList"]:
|
||||||
|
@ -93,7 +93,12 @@ class OngekiBright(OngekiBase):
|
|||||||
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
user_characters = self.data.item.get_characters(data["userId"])
|
user_characters = self.data.item.get_characters(data["userId"])
|
||||||
if user_characters is None:
|
if user_characters is None:
|
||||||
return {}
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": 0,
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userCharacterList": []
|
||||||
|
}
|
||||||
|
|
||||||
max_ct = data["maxCount"]
|
max_ct = data["maxCount"]
|
||||||
next_idx = data["nextIndex"]
|
next_idx = data["nextIndex"]
|
||||||
@ -543,7 +548,7 @@ class OngekiBright(OngekiBase):
|
|||||||
"returnCode": 1,
|
"returnCode": 1,
|
||||||
"orderId": 0,
|
"orderId": 0,
|
||||||
"serialId": "11111111111111111111",
|
"serialId": "11111111111111111111",
|
||||||
"apiName": "CMUpsertUserPrintPlaylogApi",
|
"apiName": "CMUpsertUserPrintPlaylogApi"
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
||||||
@ -551,7 +556,7 @@ class OngekiBright(OngekiBase):
|
|||||||
"returnCode": 1,
|
"returnCode": 1,
|
||||||
"orderId": 0,
|
"orderId": 0,
|
||||||
"serialId": "11111111111111111111",
|
"serialId": "11111111111111111111",
|
||||||
"apiName": "CMUpsertUserPrintlogApi",
|
"apiName": "CMUpsertUserPrintlogApi"
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
||||||
|
@ -142,8 +142,8 @@ class OngekiBrightMemory(OngekiBright):
|
|||||||
user_data = super().handle_cm_get_user_data_api_request(data)
|
user_data = super().handle_cm_get_user_data_api_request(data)
|
||||||
|
|
||||||
# hardcode Card Maker version for now
|
# hardcode Card Maker version for now
|
||||||
# Card Maker 1.34.00 = 1.30.01
|
# Card Maker 1.34 = 1.30.01
|
||||||
# Card Maker 1.36.00 = 1.35.04
|
# Card Maker 1.35 = 1.35.03
|
||||||
user_data["userData"]["compatibleCmVersion"] = "1.35.04"
|
user_data["userData"]["compatibleCmVersion"] = "1.35.03"
|
||||||
|
|
||||||
return user_data
|
return user_data
|
||||||
|
@ -3,7 +3,8 @@ import json
|
|||||||
import inflection
|
import inflection
|
||||||
import yaml
|
import yaml
|
||||||
import string
|
import string
|
||||||
import logging, coloredlogs
|
import logging
|
||||||
|
import coloredlogs
|
||||||
import zlib
|
import zlib
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from os import path
|
from os import path
|
||||||
@ -93,7 +94,7 @@ class OngekiServlet:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
if url_path.lower() == "/ping":
|
if url_path.lower() == "ping":
|
||||||
return zlib.compress(b'{"returnCode": 1}')
|
return zlib.compress(b'{"returnCode": 1}')
|
||||||
|
|
||||||
req_raw = request.content.getvalue()
|
req_raw = request.content.getvalue()
|
||||||
|
@ -706,7 +706,7 @@ class OngekiItemData(BaseData):
|
|||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
user=aime_id, serialId=serial_id, **user_print_data
|
user=aime_id, **user_print_data
|
||||||
)
|
)
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user