71 Commits
v1.3 ... v2.8

Author SHA1 Message Date
Lost-MSth
5068c7215c Merge pull request #34 from Lost-MSth/test
Update to v2.8
2022-01-20 16:43:31 +08:00
Lost-MSth
99f05cabb7 Update README.md 2022-01-20 16:42:33 +08:00
Lost-MSth
1a097bc4d0 Update to v2.8 2022-01-20 16:39:57 +08:00
Lost-MSth
849f4f7260 Add Link Play 2022-01-19 17:52:32 +08:00
Lost-MSth
add81ee639 Update README.md 2022-01-18 15:39:27 +08:00
Lost-MSth
b927ad23f8 Update to v2.7.2 without release
- Unlock the character **marija**
- Update the song database
2021-12-26 21:29:12 +08:00
Lost-MSth
8bc74b36c7 Update to v2.7.1 without release
- Unlock the character **shirahime**
- Fix the values of two characters
- Update the song database

> It will take more time for me to make the Link Play system. I am not sure how much time it will take, because it is a new challenge for me.
2021-12-13 00:26:01 +08:00
Lost-MSth
7dc8bfea9a Fix a bug
#32
Fix a bug about register
2021-11-21 13:57:09 +08:00
Lost-MSth
d0be49fe20 Update to v2.7.0 2021-11-14 21:35:34 +08:00
Lost-MSth
b494b93c14 Update to v2.6.6 without release
- Fix a bug about purchase discount
- Fix a bug about database synchronization, which may make 'api_login' table empty
- For Arcaea 3.8.8
- Update a logout api
2021-10-22 20:00:22 +08:00
Lost-MSth
a523ff5aae Update README.md
Add some download links.

close #28
2021-10-01 16:24:56 +08:00
Lost-MSth
acbe9fc4a1 Merge pull request #31 from superDXZ/master
Try to add some download links.
2021-10-01 16:19:53 +08:00
superDXZ
9422bf7182 Update README.md
Found another download site
2021-10-01 02:49:06 +08:00
superDXZ
53acad4ffb Update README.md
Found another download site
2021-10-01 02:46:39 +08:00
Lost-MSth
13048e57f4 Update to v2.6.5 without release
- Change something to make that purchase things can have some cores.
- Fix the download link of Arcaea client in README

> This is a small update.
2021-09-30 19:02:10 +08:00
Lost-MSth
39d3f073ba Update to v2.6.4 without release 2021-09-08 17:35:10 +08:00
Lost-MSth
2cf6595478 Update to v2.6.3 2021-08-25 14:31:06 +08:00
Lost-MSth
7c3bc99570 Update to v2.6.2 2021-08-11 18:45:32 +08:00
Lost-MSth
168e5f0b12 Update to v2.6.1 2021-07-21 18:14:26 +08:00
Lost-MSth
9286caf900 Update to v2.6 2021-07-01 18:46:16 +08:00
Lost-MSth
b0647bedb6 Merge pull request #24 from MBRjun/patch-1
Update run.bat

To deal with the problem when you run *run.bat* with Administrator power and in another path.
2021-06-20 22:07:52 +08:00
MBRjun
f49a868707 Update run.bat 2021-06-20 22:04:41 +08:00
MBRjun
28bca25092 Update run.bat
cd %~dp0 用于cd到当前目录
使用管理员权限或Administrator账户运行脚本默认路径为%windir%\system32
2021-06-20 21:47:02 +08:00
Lost-MSth
0227d2b44a Merge pull request #22 from Menci/sort_friend_list
Sort friend list
2021-06-17 13:03:54 +08:00
Lost-MSth
0d95b886bc Merge pull request #23 from Menci/use_b10_as_r10
Add USE_B10_AS_R10 option
2021-06-17 12:53:33 +08:00
Menci
9b61acd163 Sort friend list 2021-06-17 03:04:39 +08:00
Menci
be134bbc6e Add USE_B10_AS_R10 option 2021-06-16 19:02:04 +00:00
Lost-MSth
c8d0463b9e Fix a small bug
Fix a bug about double slashes in URL, which will influence character uncap, uncap override and level up with EXP cores when you use a special game API prefix, like '/latte' or '/latte/1'.
It means that the length of game API prefix is not 2 in the end (not '/*** . . . **/**') and the bug will happen.
2021-06-14 18:04:16 +08:00
Lost-MSth
871c2082a8 Fix two small bugs
1. Rank list numbers in my world rank may disappear.
2.  Characters in rank list may be locked to uncapping state.
2021-06-10 22:27:29 +08:00
Lost-MSth
14b685850c Update README.md 2021-06-10 20:12:32 +08:00
Lost-MSth
3fbf473de2 Ready to update to v2.5
Please wait for some time. I need to change readme. I almost forgot what I changed before.
WARNING: There will be many bugs!
2021-06-10 19:01:26 +08:00
Lost-MSth
8a3cec05b7 Update to v2.4 2021-05-14 14:06:58 +08:00
Lost-MSth
e3d81436d0 Waiting for updating
+ new character
+ new byd map
+ new items
+ some new configs
+ two new operations in background
+ record email while registering
+ record ip while logging in
+ checking something before running and updating database automatically

building something about API

fix bugs:
about purchasing system
about hiding ptt
about login different accounts with same device
about some details

This is only a pre updating. Many things have been changed. It takes time to find some ways.
2021-05-12 00:45:13 +08:00
Lost-MSth
941a79ccc7 Update to v2.3.2 2021-04-09 14:42:14 +08:00
Lost-MSth
62902a561c Add an option 2021-04-05 14:45:50 +08:00
Lost-MSth
1660bc5899 Merge branch 'master' of https://github.com/Lost-MSth/Arcaea-server 2021-04-05 13:20:42 +08:00
Lost-MSth
d577246d28 Revert "Revert "Fix some bugs""
This reverts commit 9fd626468e.
2021-04-05 13:20:36 +08:00
Lost-MSth
9fd626468e Revert "Fix some bugs"
This reverts commit c51a1b4e85.
2021-04-05 13:19:32 +08:00
Lost-MSth
c51a1b4e85 Fix some bugs
Fix the fanmade cloud save (add a switch in setting)

Add SSL switch

Fix 'my rank' in game, if there is not enough people behind you, now it will show 20 people as expected
2021-04-05 13:02:56 +08:00
Lost-MSth
0e42123417 Update README.md
Well, it is not 4.1 now.
2021-04-02 22:03:35 +08:00
Lost-MSth
e216e6144b Update README.md 2021-04-01 17:05:53 +08:00
Lost-MSth
b70c435a4a Update to v2.3.1 2021-04-01 17:02:56 +08:00
Lost-MSth
eb03d2be93 Update README.md 2021-03-14 22:37:59 +08:00
Lost-MSth
fcb5d264ef Add something and fix something
Add character values.
Do something about #16.
Fix a problem about relative path.
2021-03-14 22:33:13 +08:00
Lost-MSth
d617951e1f Fix a bug(Follow last commit)
Ctrl c and Ctrl v,
A bug appears!
2021-03-09 20:09:58 +08:00
Lost-MSth
febf931f1a Fix something
I don't know if it is a bug and I haven't test it.

Maybe it makes low version arcaea can log in.
2021-03-09 19:47:54 +08:00
Lost-MSth
981961d6a1 Update README.md 2021-03-09 14:20:54 +08:00
Lost-MSth
a90380df4c Create error.log 2021-03-09 13:55:09 +08:00
Lost-MSth
4f66f492fc Update to v2.3 2021-03-09 13:46:49 +08:00
Lost-MSth
df181f104e Update to v2.2
close #13
2021-02-11 15:26:58 +08:00
Lost-MSth
a890a9af4a Waiting for updating and fix some bugs
It will be released soon because lowiro will update in some days.
Fix many bugs and I forgot them. :<

I push this because there's a small but serious safety problem. It should be fixed immediately.

Oh, and this update needs you to login in arcaea again because a new function is added.
2021-02-08 14:45:10 +08:00
Lost-MSth
c6bb4c9afb Update to v2.1 2021-01-21 14:42:57 +08:00
Lost-MSth
98358ad216 Fix two small bugs. 2021-01-10 16:29:18 +08:00
Lost-MSth
e0e6128826 Update README.md 2021-01-08 14:53:50 +08:00
Lost-MSth
08872399cc Update to v2.0
close #11
close #10
close #9
2021-01-08 14:51:37 +08:00
Lost-MSth
29a6cfaac1 Fix a bug. 2021-01-02 15:09:34 +08:00
Lost-MSth
f25bd4130e Update a midway version (waiting for offical update)
Add some things and fix some bugs.
It will not have a description.
2020-12-29 11:29:36 +08:00
Lost-MSth
68c757f9d7 Fix a bug 2020-12-22 13:07:49 +08:00
Lost-MSth
26216bf84a Update to v1.9 2020-12-17 17:59:19 +08:00
Lost-MSth
5771d0c669 Something wrong
Add login table primary key
2020-12-09 21:13:30 +08:00
Lost-MSth
80122d749e Update to v1.8.1
close #6
close #8
2020-12-09 21:06:27 +08:00
Lost-MSth
5c02ecdf37 Update to v1.8
close #4
close #7
2020-12-03 21:54:24 +08:00
Lost-MSth
dfcbb6f9c0 Update to v1.7
close #1
close #2
2020-11-27 21:35:35 +08:00
Lost-MSth
30690b2fbf Update to v1.6 2020-11-19 22:15:33 +08:00
Lost-MSth
56ae7047fc Update to v1.5
A small update.
2020-11-02 19:02:50 +08:00
Lost-MSth
f5d2589971 Update README.md 2020-10-31 15:03:46 +08:00
Lost-MSth
8bb5c28663 Update README.md 2020-10-30 20:49:25 +08:00
Lost-MSth
123554e24d Update to v1.4 2020-10-30 20:17:21 +08:00
Lost-MSth
bb7ba249e9 change something 2020-10-18 17:49:33 +08:00
Lost-MSth
3ecefcb802 Update README.md 2020-10-18 17:39:14 +08:00
Lost-MSth
798a838ca4 Update README.md 2020-10-18 17:37:01 +08:00
157 changed files with 18373 additions and 29859 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.log

108
README.md
View File

@@ -2,60 +2,97 @@
一个微型的Arcaea本地服务器 A small local server for Arcaea
## 简介 Introduction
这是基于Python以及Flask的微型本地Arcaea服务器可以模拟游戏的主要功能,一时兴起在五天之内糊了出来。这可能是我第一次写这种大程序,若有不妥之处,敬请谅解。
This is a small local Arcaea server based on Python and Flask, which can simulate the main functions of the game. I completed it on the spur of the moment in five days. This may be the first time I have written such a large program. Please understand if there is something wrong with it.
这是基于Python以及Flask的微型本地Arcaea服务器可以模拟游戏的主要功能。这可能是我第一次写这种大程序若有不妥之处敬请谅解。
本程序主要用于学习研究,不得用于任何商业行为,否则后果自负,这不是强制要求,只是一个提醒与警告。
This is a small local Arcaea server based on Python and Flask, which can simulate the main functions of the game. This may be the first time I have written such a large program. Please understand if there is something wrong with it.
This procedure is mainly used for study and research, and shall not be used for any commercial activities, otherwise the consequences will be borne by oneself. This is not a mandatory requirement, just a reminder and warning.
> 虽然看起来很蠢,但是可以用!
> It looks stupid, but it works!
## 特性 Features
有以下 We have
- 登录、注册 Login and registration
- 多设备登录 Multi device login
- 成绩上传 Score upload
- PTT
- 世界排名 Global rank
- 排名 Rank
- Link Play
- 好友系统 Friends
- 数据同步 Data synchronization
- 爬梯 Climbing steps
- 自定义世界模式 Customizable World Mode
- 自定义歌曲下载 Customizable songs download
- 单曲和曲包购买(没啥用) Single songs and song packs purchase(useless)
- 奖励系统 Present system
- 兑换码系统 Redeem code system
- 角色系统 Character system
- 全剧情解锁 Unlock all the stories
- 后台查询 Background search
- 后台自定义信息 Customize some things in the background
- 成绩校验 Score check
- 下载校验 Download check
- 服务器日志 Server log
没有以下 We don't have
- 服务器安全性保证 Server security assurance
可能有问题 There may be problems
- Recent 30
- 一些歌曲的解锁 Some songs' unlocking
- 同设备多共存登录 Multiple app logins on the same device
## 说明 Statement
只是很有趣,用处探索中。
It is just so interesting. What it can do is under exploration.
## 下载 Download
[这里 Here](https://github.com/Lost-MSth/Arcaea-server/releases)
[Arcaea-CN official](https://arcaea.lowiro.com/zh)
[Arcaea-lowi.ro](https://lowi.ro)
[Arcaea-RhyDown](https://rhydown.com)
## 更新日志 Update log
### Version 1.3
- 适用于Arcaea 3.2.1版本 For Arcaea 3.2.1
- 新增了可自定义的世界模式 Add customizable World Mode.
- 对于官方版本,解锁几乎所有歌曲 For the official version, unlock almost all songs.
只保留最新版本 Only keep the latest version.
> 更新说明本次更新无法从旧版本同步数据请使用游戏内数据同步功能上传best_score数据
> 提醒:更新时请注意保留原先的数据库,以防数据丢失。
>
> Tips: When updating, please keep the original database in case of data loss.
> Update note: In this update you cannot synchronize data from an older version. Please use the data synchronization function in game to upload best_score data.
### Version 1.2
- 仍然适用于Arcaea 3.2.0版本 Still for Arcaea 3.2.0
- 新增了网页管理页面,可以通过 http:// HOST IP : PORT /web 进行访问 Add a new management webpage, you can visit http:// HOST IP : PORT /web to use it.
- 修改了character表结构这将方便以后的角色更新 The character table structure has been modified, which will facilitate the character update in the future.
- 修复了best_score表的时间记录应该是精确到秒的Unix时间戳 Fix the time record of best_score table. It should be a UNIX timestamp accurate to seconds.
- 修复了一个bugrecent30现在会考虑同一首歌的不同难度 Fix a bug. Now recent30 will consider different difficulties of one song.
> 更新说明现在你可以使用web管理页面来迁移旧数据了后台固定账号admin密码admin
> 如果使用数据迁移那么请先清空recent30表数据或者在迁移后打30首歌手动清除recent30这次更新后web页面读取原来的recent30表将会报错
> Update note: Now you can use the web management page to migrate old data. Background account is fixed. Username is admin, and password is admin.
> If you use data migration, please clear the data in the recent30 table first, or manually clear recent30 by playing 30 songs after the migration. After this update, an error will be reported when the web page reads the past recent30 table.
### Version 1.1
- 适用于Arcaea 3.2.0版本 For Arcaea 3.2.0
### Version 2.8
- 适用于Arcaea 3.11.2版本 For Arcaea 3.11.2
- 更新了歌曲数据库 Update the song database.
- 角色**梦**可以看到觉醒立绘了 The drawing of uncapped character **Yume** can be seen.
- 修复了一个Bug现在剧情可以全部解锁了 Fix a bug. Now the plot can be unlocked.
> 更新说明:您若需要做数据迁移,请使用原来的数据库文件**arcaea_database.db**并使用数据库操作软件打开数据库修改角色数据SQL命令为
> `Update user_char set is_uncapped=1, is_uncapped_override=1 where character_id=21;`
- 新增对Link Play的支持 Add support for Link Play.
- 搭档**彩梦**已觉醒 Uncap the character **ayu**.
> Update note: If you need to do data migration, please use the original database file **arcaea_ database.db**, and use the database operation software to open the database to modify the character data. The SQL command is
> `Update user_char set is_uncapped=1, is_uncapped_override=1 where character_id=21;`
- 以下是累积更新 The following are cumulative updates:
- 修复注册接口端点多了个杠的问题 Fix an unexpected slash at the registered interface endpoint.
- 新搭档**白姬**已解锁 Unlock the character **shirahime**.
- 修正两个角色的数值问题 Fix the values of two characters.
- 新搭档**玛莉嘉**已解锁 Unlock the character **marija**.
> 提示Link Play可能存在大量bug
> Tips: There may be many bugs in Link Play system.
### Version 1.0
- 适用于Arcaea 3.1.2版本 For Arcaea 3.1.2
## 运行环境与依赖 Running environment and requirements
- Windows操作系统 Windows operating system
- Windows/Linux/Mac OS/Android...
- Python 3
- Flask模块 Flask module
- Charles
- Flask module, Cryptography module
- Charles, IDA, proxy app... (optional)
<!--
## 环境搭建 Environment construction
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA)
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Environment-construction)
-->
## 使用说明 Instruction for use
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
@@ -65,9 +102,12 @@ It is just so interesting. What it can do is under exploration.
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E6%B3%A8%E6%84%8F)
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Attentions)
## 鸣谢 Thanks
歌曲数据库来自 Using song database from
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)
网站图标来自 Using favicon from [black fate - てんてん - pixiv](https://www.pixiv.net/artworks/82374369)
## 联系方式 Contact
如有必要,可以联系本人 Contact me if necessary

View File

View File

@@ -0,0 +1,156 @@
import hashlib
import base64
import time
import random
from server.sql import Connect
import functools
from setting import Config
from flask import jsonify
class User():
# 用户类,当数据类型用
def __init__(self, user_id=None, role='', power=[]):
self.user_id = user_id
self.role = role
self.power = power
def login(auth: str, ip: str):
# 登录接口,返回字典和错误码
try:
auth_decode = bytes.decode(base64.b64decode(auth))
except:
return {}, -100
if not ':' in auth_decode:
return {}, -100
name, password = auth_decode.split(':', 1)
with Connect() as c:
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute('''select user_id, password from user where name = :name''', {
'name': name})
x = c.fetchone()
if x is None:
return {}, -201
if x[1] == '':
return {}, -202
if hash_pwd != x[1]:
return {}, -201
user_id = str(x[0])
now = int(time.time() * 1000)
token = hashlib.sha256(
(user_id + str(random.randint(10000, 99999)) + str(now)).encode("utf8")).hexdigest()
c.execute('''delete from api_login where user_id=?''', (user_id,))
c.execute('''insert into api_login values(?,?,?,?)''',
(user_id, token, now, ip))
return {'token': token, 'user_id': user_id}, 0
def logout(user: User):
# 登出接口,返回错误码
code = 999
with Connect() as c:
c.execute('''delete from api_login where user_id=?''', (user.user_id,))
code = 0
return code
def id_get_role_id(c, user_id):
# user_id获取role_id
role_id = 1
c.execute('''select role_id from user_role where user_id = :a''',
{'a': user_id})
x = c.fetchone()
if x is not None:
role_id = int(x[0])
return role_id
def role_id_get_role(c, role_id):
# role_id获取role
role = ''
c.execute('''select role_name from role where role_id = :a''',
{'a': role_id})
x = c.fetchone()
if x is not None:
role = x[0]
return role
def api_token_get_id(c, token):
# api的token获取user_id
user_id = None
c.execute('''select user_id from api_login where token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def get_role_power(c, role_id):
# 获取role_id对应power返回列表
role_power = []
c.execute('''select power_name from power where power_id in (select power_id from role_power where role_id=:a)''', {
'a': role_id})
x = c.fetchall()
for i in x:
role_power.append(i[0])
return role_power
def role_required(request, power=[]):
# api token验证写成了修饰器
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
try:
request.json # 检查请求json格式
except:
return jsonify({'status': 400, 'code': -1, 'data': {}, 'msg': 'Payload must be a valid json'})
if not 'Token' in request.headers:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'})
user = User()
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
user.user_id = 0
elif power == []:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
else:
with Connect() as c:
user.user_id = api_token_get_id(
c, request.headers['Token'])
if user.user_id is None:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'})
role_id = id_get_role_id(c, user.user_id)
user.role = role_id_get_role(c, role_id)
user.role_power = get_role_power(c, role_id)
f = False
for i in power:
if i in user.role_power:
f = True
break
if not f:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return view(user, *args, **kwargs)
return wrapped_view
return decorator

View File

@@ -0,0 +1,19 @@
def code_get_msg(code):
# api接口code获取msg返回字符串
msg = {
'0': '',
'-1': 'See status code',
'-2': 'No data',
'-3': 'No data or user',
'-4': 'No user_id',
'-100': 'Wrong post data',
'-101': 'Wrong data type',
'-102': 'Wrong query parameter',
'-103': 'Wrong sort parameter',
'-104': 'Wrong sort order parameter',
'-201': 'Wrong username or password',
'-202': 'User is banned',
'-999': 'Unknown error'
}
return msg[str(code)]

View File

@@ -0,0 +1,228 @@
from flask import (
Blueprint, request, jsonify
)
import functools
import api.api_auth
import api.users
import api.songs
from api.api_code import code_get_msg
bp = Blueprint('api', __name__, url_prefix='/api/v1')
class Query():
# 查询类,当查询附加参数的数据类型用
def __init__(self, limit=-1, offset=0, query={}, sort=[]) -> None:
self.limit = limit
self.offset = offset
self.query = query # {'name': 'admin'}
self.sort = sort # [{'column': 'user_id', 'order': 'ASC'}, ...]
def get_query_parameter(request, query_able=[], sort_able=[]):
# 提取查询请求参数返回Query类查询参数写成修饰器
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
re = Query()
if 'limit' in request.json:
try:
re.limit = int(request.json['limit'])
except:
return jsonify({'status': 200, 'code': -101, 'data': {}, 'msg': code_get_msg(-101)})
if 'offset' in request.json:
try:
re.offset = int(request.json['offset'])
except:
return jsonify({'status': 200, 'code': -101, 'data': {}, 'msg': code_get_msg(-101)})
if 'query' in request.json:
re.query = request.json['query']
for i in re.query:
if i not in query_able:
return jsonify({'status': 200, 'code': -102, 'data': {}, 'msg': code_get_msg(-102)})
if 'sort' in request.json:
re.sort = request.json['sort']
for i in re.sort:
if 'column' not in i or i['column'] not in sort_able:
return jsonify({'status': 200, 'code': -103, 'data': {}, 'msg': code_get_msg(-103)})
if not 'order' in i:
i['order'] = 'ASC'
else:
if i['order'] not in ['ASC', 'DESC']:
return jsonify({'status': 200, 'code': -104, 'data': {}, 'msg': code_get_msg(-104)})
return view(re, *args, **kwargs)
return wrapped_view
return decorator
def return_encode(code: int = 0, data: dict = {}, status: int = 200, msg: str = ''):
# 构造返回返回jsonify处理过后的response_class
if msg == '':
msg = code_get_msg(code)
if code < 0:
return jsonify({'status': status, 'code': code, 'data': {}, 'msg': msg})
else:
return jsonify({'status': status, 'code': code, 'data': data, 'msg': msg})
@bp.route('/')
def ping():
return return_encode()
@bp.route('/token', methods=['POST'])
def token_post():
# 登录获取token
# {'auth': `base64(user_id:password)`}
if 'auth' in request.json:
data, code = api.api_auth.login(
str(request.json['auth']), request.remote_addr)
return return_encode(code, data)
else:
return return_encode(-1, {}, 401, 'No authentication')
@bp.route('/token', methods=['GET'])
@api.api_auth.role_required(request, ['select_me', 'select'])
def token_get(user):
# 判断登录有效性
return return_encode()
@bp.route('/token', methods=['DELETE'])
@api.api_auth.role_required(request, ['change_me', 'select_me', 'select'])
def token_delete(user):
# 登出
return return_encode(api.api_auth.logout(user))
@bp.route('/users', methods=['GET'])
@api.api_auth.role_required(request, ['select'])
@get_query_parameter(request, ['user_id', 'name', 'user_code'], [
'user_id', 'name', 'user_code', 'join_date', 'rating_ptt', 'time_played', 'ticket', 'world_rank_score'])
def users_get(query, user):
# 查询全用户信息
data = api.users.get_users(query)
if not data:
return return_encode(-2)
return return_encode(0, data)
@bp.route('/users/<int:user_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_get(user, user_id):
# 查询用户信息
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_info(user_id)
if not data:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/b30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_b30_get(user, user_id):
# 查询用户b30
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_b30(user_id)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/best', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
@get_query_parameter(request, ['song_id', 'difficulty'], [
'song_id', 'difficulty', 'score', 'time_played', 'rating'])
def users_user_best_get(query, user, user_id):
# 查询用户所有best成绩
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_best(user_id, query)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/r30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_r30_get(user, user_id):
# 查询用户r30
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_r30(user_id)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/songs/<string:song_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
def songs_song_get(user, song_id):
# 查询歌曲信息
data = api.songs.get_song_info(song_id)
if not data:
return return_encode(-2)
return return_encode(0, data)
@bp.route('/songs', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
@get_query_parameter(request, ['sid', 'name_en', 'name_jp', 'pakset', 'artist'], [
'sid', 'name_en', 'name_jp', 'pakset', 'artist', 'date', 'rating_pst', 'rating_prs', 'rating_ftr', 'rating_byn'])
def songs_get(query, user):
# 查询全歌曲信息
data = api.songs.get_songs(query)
if not data:
return return_encode(-2)
return return_encode(0, data)

View File

@@ -0,0 +1,58 @@
from server.sql import Connect
from server.sql import Sql
import time
def get_song_info(song_id):
# 查询指定歌曲信息,返回字典
r = {}
with Connect('./database/arcsong.db') as c:
c.execute('''select * from songs where sid=:a''', {'a': song_id})
x = c.fetchone()
if x:
r = {'song_id': x[0],
'name': {'name_en': x[1],
'name_jp': x[2]},
'pakset': x[5],
'artist': x[6],
'date': x[9],
'rating_pst': x[13]/10,
'rating_prs': x[14]/10,
'rating_ftr': x[15]/10,
'rating_byn': x[16]/10,
'difficultly_pst': x[17]/2,
'difficultly_prs': x[18]/2,
'difficultly_ftr': x[19]/2,
'difficultly_byn': x[20]/2
}
return r
def get_songs(query=None):
# 查询全部歌曲信息,返回字典列表
r = []
with Connect('./database/arcsong.db') as c:
x = Sql.select(c, 'songs', [], query)
if x:
for i in x:
r.append({'sid': i[0],
'name': {'name_en': i[1],
'name_jp': i[2]},
'pakset': i[5],
'artist': i[6],
'date': i[9],
'rating_pst': i[13]/10,
'rating_prs': i[14]/10,
'rating_ftr': i[15]/10,
'rating_byn': i[16]/10,
'difficultly_pst': i[17]/2,
'difficultly_prs': i[18]/2,
'difficultly_ftr': i[19]/2,
'difficultly_byn': i[20]/2
})
return r

View File

@@ -0,0 +1,99 @@
from server.sql import Connect
from server.sql import Sql
import time
import web.webscore
import server.info
def get_users(query=None):
# 获取全用户信息,返回字典列表
r = []
with Connect() as c:
x = Sql.select(c, 'user', [], query)
if x:
for i in x:
if i[23] != -1:
character_id = i[23]
else:
character_id = i[6]
r.append({
'user_id': i[0],
'name': i[1],
'join_date': i[3],
'user_code': i[4],
'rating_ptt': i[5]/100,
'character_id': character_id,
'is_char_uncapped': i[8] == 1,
'is_char_uncapped_override': i[9] == 1,
'is_hide_rating': i[10],
'ticket': i[26]
})
return r
def get_user_info(user_id):
# 获取用户信息返回字典其实就是调用user/me信息
r = {}
with Connect() as c:
r = server.info.get_user_me(c, user_id)
return r
def get_user_b30(user_id):
# 获取用户b30信息返回字典
r = []
with Connect() as c:
r = web.webscore.get_user_score(c, user_id, 30)
bestptt = 0
for i in r:
if i['rating']:
bestptt += i['rating']
if 'time_played' in i:
i['time_played'] = int(time.mktime(time.strptime(
i['time_played'], '%Y-%m-%d %H:%M:%S')))
return {'user_id': user_id, 'b30_ptt': bestptt / 30, 'data': r}
def get_user_best(user_id, query=None):
# 获取用户b30信息返回字典
r = []
with Connect() as c:
x = Sql.select(c, 'best_score', [], query)
if x:
for i in x:
r.append({
"song_id": i[1],
"difficulty": i[2],
"score": i[3],
"shiny_perfect_count": i[4],
"perfect_count": i[5],
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": i[10],
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13]
})
return {'user_id': user_id, 'data': r}
def get_user_r30(user_id):
# 获取用户r30信息返回字典
r = []
with Connect() as c:
r, r10_ptt = web.webscore.get_user_recent30(c, user_id)
return {'user_id': user_id, 'r10_ptt': r10_ptt, 'data': r}

View File

Binary file not shown.

View File

@@ -0,0 +1,525 @@
import sqlite3
import time
import json
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
ARCAEA_SERVER_VERSION = 'v2.8'
def main(path='./'):
conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor()
c.execute('''create table if not exists config(id text primary key,
value text
);''')
c.execute('''insert into config values("version", :a);''',
{'a': ARCAEA_SERVER_VERSION})
c.execute('''create table if not exists user(user_id int primary key,
name text unique,
password text not null,
join_date char(20),
user_code char(10),
rating_ptt int,
character_id int,
is_skill_sealed int,
is_char_uncapped int,
is_char_uncapped_override int,
is_hide_rating int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
clear_type int,
rating real,
favorite_character int,
max_stamina_notification_enabled int,
current_map text,
ticket int,
prog_boost int,
email text,
world_rank_score int,
ban_flag text,
next_fragstam_ts int,
max_stamina_ts int,
stamina int
);''')
c.execute('''create table if not exists login(access_token text,
user_id int,
login_time int,
login_ip text,
login_device text,
primary key(access_token, user_id)
);''')
c.execute('''create table if not exists friend(user_id_me int,
user_id_other int,
primary key (user_id_me, user_id_other)
);''')
c.execute('''create table if not exists best_score(user_id int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
best_clear_type int,
clear_type int,
rating real,
primary key(user_id, song_id, difficulty)
);''')
c.execute('''create table if not exists user_char(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);''')
c.execute('''create table if not exists user_char_full(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);''')
c.execute('''create table if not exists character(character_id int primary key,
name text,
max_level int,
frag1 real,
prog1 real,
overdrive1 real,
frag20 real,
prog20 real,
overdrive20 real,
frag30 real,
prog30 real,
overdrive30 real,
skill_id text,
skill_unlock_level int,
skill_requires_uncap int,
skill_id_uncap text,
char_type int,
is_uncapped int
);''')
c.execute('''create table if not exists char_item(character_id int,
item_id text,
type text,
amount int,
primary key(character_id, item_id, type)
);''')
c.execute('''create table if not exists recent30(user_id int primary key,
r0 real,
song_id0 text,
r1 real,
song_id1 text,
r2 real,
song_id2 text,
r3 real,
song_id3 text,
r4 real,
song_id4 text,
r5 real,
song_id5 text,
r6 real,
song_id6 text,
r7 real,
song_id7 text,
r8 real,
song_id8 text,
r9 real,
song_id9 text,
r10 real,
song_id10 text,
r11 real,
song_id11 text,
r12 real,
song_id12 text,
r13 real,
song_id13 text,
r14 real,
song_id14 text,
r15 real,
song_id15 text,
r16 real,
song_id16 text,
r17 real,
song_id17 text,
r18 real,
song_id18 text,
r19 real,
song_id19 text,
r20 real,
song_id20 text,
r21 real,
song_id21 text,
r22 real,
song_id22 text,
r23 real,
song_id23 text,
r24 real,
song_id24 text,
r25 real,
song_id25 text,
r26 real,
song_id26 text,
r27 real,
song_id27 text,
r28 real,
song_id28 text,
r29 real,
song_id29 text
);''')
c.execute('''create table if not exists user_world(user_id int,
map_id text,
curr_position int,
curr_capture real,
is_locked int,
primary key(user_id, map_id)
);''')
c.execute('''create table if not exists world_songplay(user_id int,
song_id text,
difficulty int,
stamina_multiply int,
fragment_multiply int,
prog_boost_multiply int,
primary key(user_id, song_id, difficulty)
);''')
c.execute('''create table if not exists download_token(user_id int,
song_id text,
file_name text,
token text,
time int,
primary key(user_id, song_id, file_name)
);''')
c.execute('''create table if not exists user_download(user_id int,
token text,
time int,
primary key(user_id, token, time)
);''')
c.execute('''create table if not exists item(item_id text,
type text,
is_available int,
_id text,
primary key(item_id, type)
);''')
c.execute('''create table if not exists user_item(user_id int,
item_id text,
type text,
amount int,
primary key(user_id, item_id, type)
);''')
c.execute('''create table if not exists purchase(purchase_name text primary key,
price int,
orig_price int,
discount_from int,
discount_to int
);''')
c.execute('''create table if not exists purchase_item(purchase_name text,
item_id text,
type text,
amount int,
primary key(purchase_name, item_id, type)
);''')
c.execute('''create table if not exists user_save(user_id int primary key,
scores_data text,
clearlamps_data text,
clearedsongs_data text,
unlocklist_data text,
installid_data text,
devicemodelname_data text,
story_data text,
createdAt int
);''')
c.execute('''create table if not exists present(present_id text primary key,
expire_ts int,
description text
);''')
c.execute('''create table if not exists user_present(user_id int,
present_id text,
primary key(user_id, present_id)
);''')
c.execute('''create table if not exists present_item(present_id text,
item_id text,
type text,
amount int,
primary key(present_id, item_id, type)
);''')
c.execute('''create table if not exists songfile(song_id text,
file_type int,
md5 text,
primary key(song_id, file_type)
);''')
c.execute('''create table if not exists redeem(code text primary key,
type int
);''')
c.execute('''create table if not exists user_redeem(user_id int,
code text,
primary key(user_id, code)
);''')
c.execute('''create table if not exists redeem_item(code text,
item_id text,
type text,
amount int,
primary key(code, item_id, type)
);''')
c.execute('''create table if not exists role(role_id int primary key,
role_name text,
caption text
);''')
c.execute('''create table if not exists user_role(user_id int,
role_id int,
primary key(user_id, role_id)
);''')
c.execute('''create table if not exists power(power_id int primary key,
power_name text,
caption text
);''')
c.execute('''create table if not exists role_power(role_id int,
power_id int,
primary key(role_id, power_id)
);''')
c.execute('''create table if not exists api_login(user_id int,
token text,
login_time int,
login_ip text,
primary key(user_id, token)
);''')
# 搭档初始化
char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', '', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin',
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija']
skill_id = ['gauge_easy', '', '', '', 'note_mirror', '', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]
frag1 = [55, 55, 60, 50, 47, 0, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32,
42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 50, 25, 58, 50, 61, 45, 45, 38]
prog1 = [35, 55, 47, 50, 60, 0, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52,
59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58]
overdrive1 = [35, 55, 25, 50, 47, 0, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18,
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 50, 30, 49, 15, 34, 45, 45, 38]
frag20 = [78, 80, 90, 75, 70, 0, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52,
65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 100, 50, 68, 60, 90, 67, 50, 60]
prog20 = [61, 80, 70, 75, 90, 0, 90, 102, 84, 78, 105, 67, 63, 68, 0, 99, 80, 66, 46, 83, 40, 73,
80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 100, 55, 65, 59, 90, 50, 90, 90]
overdrive20 = [61, 80, 47, 75, 70, 0, 95, 79, 65, 31, 50, 59, 90, 58, 0, 78, 50, 70, 62, 49, 64,
46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 100, 40, 69, 62, 51, 90, 67, 60]
frag30 = [88, 90, 100, 75, 80, 0, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 100, 50, 68, 60, 90, 67, 50, 60]
prog30 = [71, 90, 80, 75, 100, 0, 90, 102, 84, 78, 105, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 100, 55, 65, 59, 90, 50, 90, 90]
overdrive30 = [71, 90, 57, 75, 80, 0, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 100, 40, 69, 62, 51, 90, 67, 60]
char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
char_core = {
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
1: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_desolate', 'amount': 25}],
2: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_crimson', 'amount': 25}],
4: [{'core_id': 'core_ambivalent', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
13: [{'core_id': 'core_scarlet', 'amount': 30}],
21: [{'core_id': 'core_scarlet', 'amount': 30}],
26: [{'core_id': 'core_chunithm', 'amount': 15}],
27: [{'core_id': 'core_chunithm', 'amount': 15}],
28: [{'core_id': 'core_chunithm', 'amount': 15}],
29: [{'core_id': 'core_chunithm', 'amount': 15}],
36: [{'core_id': 'core_chunithm', 'amount': 15}],
42: [{'core_id': 'core_chunithm', 'amount': 15}],
43: [{'core_id': 'core_chunithm', 'amount': 15}],
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
19: [{'core_id': 'core_colorful', 'amount': 30}]
}
for i in range(0, 54):
skill_requires_uncap = 1 if i == 2 else 0
if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21, 42, 43, 11, 12, 19]:
sql = '''insert into character values(?,?,30,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
else:
if i != 5 and i != 46:
sql = '''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
c.execute('''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)''', (99,
'shirahime', 38, 33, 28, 66, 58, 50, 66, 58, 50, 'frags_preferred_song', 0, 0, '', 0))
for i in char_core:
for j in char_core[i]:
c.execute('''insert into char_item values(?,?,'core',?)''',
(i, j['core_id'], j['amount']))
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful']
for i in cores:
c.execute('''insert into item values(?,"core",1,'')''', (i,))
world_songs = ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster",
"cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3"]
for i in world_songs:
c.execute('''insert into item values(?,"world_song",1,'')''', (i,))
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5"]
for i in world_unlocks:
c.execute('''insert into item values(?,"world_unlock",1,'')''', (i,))
c.execute('''insert into item values(?,?,?,?)''',
('fragment', 'fragment', 1, ''))
c.execute('''insert into item values(?,?,?,?)''',
('memory', 'memory', 1, ''))
def insert_items(c, items):
# 物品数据导入
for i in items:
if 'discount_from' not in i:
discount_from = -1
else:
discount_from = i['discount_from']
if 'discount_to' not in i:
discount_to = -1
else:
discount_to = i['discount_to']
c.execute('''insert into purchase values(?,?,?,?,?)''',
(i['name'], i['price'], i['orig_price'], discount_from, discount_to))
for j in i['items']:
if "_id" not in j:
_id = ''
else:
_id = j['_id']
c.execute(
'''select exists(select * from item where item_id=?)''', (j['id'],))
if c.fetchone() == (0,):
c.execute('''insert into item values(?,?,?,?)''',
(j['id'], j['type'], j['is_available'], _id))
if 'amount' in j:
amount = j['amount']
else:
amount = 1
c.execute('''insert into purchase_item values(?,?,?,?)''',
(i['name'], j['id'], j['type'], amount))
# item初始化
f = open(path+'singles.json', 'r')
singles = json.load(f)
f.close()
insert_items(c, singles)
f = open(path+'packs.json', 'r')
packs = json.load(f)
f.close()
insert_items(c, packs)
# api权限与权限组初始化
role = ['admin', 'user', 'selecter']
role_caption = ['管理员', '用户', '查询接口']
power = ['select', 'select_me', 'change', 'change_me', 'grant',
'grant_inf', 'select_song_rank', 'select_song_info']
power_caption = ['总体查询权限', '自我查询权限', '总体修改权限',
'自我修改权限', '授权权限', '下级授权权限', '歌曲排行榜查询权限', '歌曲信息查询权限']
role_power = {'0': [0, 1, 3, 5, 6, 7],
'1': [1, 3, 6, 7],
'2': [0]
}
for i in range(0, len(role)):
c.execute('''insert into role values(:a,:b,:c)''', {
'a': i, 'b': role[i], 'c': role_caption[i]})
for i in range(0, len(power)):
c.execute('''insert into power values(:a,:b,:c)''', {
'a': i, 'b': power[i], 'c': power_caption[i]})
for i in role_power:
for j in role_power[i]:
c.execute('''insert into role_power values(:a,:b)''',
{'a': int(i), 'b': j})
conn.commit()
conn.close()
def arc_register(name: str):
def build_user_code(c):
return '123456789'
def build_user_id(c):
return 2000000
def insert_user_char(c, user_id):
# 为用户添加可用角色
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 0, 30, 25000, 1, 0))
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 1, 30, 25000, 1, 0))
c.execute(
'''select character_id, max_level, is_uncapped from character''')
x = c.fetchall()
if x:
for i in x:
exp = 25000 if i[1] == 30 else 10000
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(user_id, i[0], i[1], exp, i[2], 0))
conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor()
hash_pwd = '41e5653fc7aeb894026d6bb7b2db7f65902b454945fa8fd65a6327047b5277fb'
c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if c.fetchone() == (0,):
user_code = build_user_code(c)
user_id = build_user_id(c)
now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket)
values(:user_id, :name, :password, :join_date, :user_code, 1250, 1, 0, 1, 0, 0, -1, 0, '', 114514)
''', {'user_code': user_code, 'user_id': user_id, 'join_date': now, 'name': name, 'password': hash_pwd})
c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id})
c.execute(
'''insert into best_score values(2000000,'vexaria',3,10000000,100,0,0,0,100,0,1599667200,3,3,10.8)''')
insert_user_char(c, user_id)
conn.commit()
conn.close()
return None
else:
conn.commit()
conn.close()
return None
arc_register('admin')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,376 @@
{
"map_id": "byd_bookmaker",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "bookmaker2",
"require_type": "chart_unlock",
"coordinate": "-650,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 300,
"character_affinity": [
23,
34,
47
],
"affinity_multiplier": [
1.3,
5,
3.8
],
"step_count": 31,
"custom_bg": "",
"curr_position": 3,
"curr_capture": 35.81730523472207,
"is_locked": false,
"steps": [
{
"map_id": "byd_bookmaker",
"position": 0,
"capture": 10
},
{
"map_id": "byd_bookmaker",
"position": 1,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 2,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 3,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
],
"restrict_ids": [
"bookmaker",
"viciousheroism",
"battlenoone",
"galaxyfriends",
"rekkaresonance"
],
"restrict_type": "song_id"
},
{
"map_id": "byd_bookmaker",
"position": 4,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 5,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 6,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 7,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 8,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 9,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 10,
"capture": 10,
"items": [
{
"type": "core",
"id": "core_generic",
"amount": 1
}
]
},
{
"map_id": "byd_bookmaker",
"position": 11,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 12,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 13,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 14,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 15,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 16,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 17,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 18,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 19,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 20,
"capture": 10,
"items": [
{
"type": "core",
"id": "core_generic",
"amount": 1
}
]
},
{
"map_id": "byd_bookmaker",
"position": 21,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 22,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 23,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 24,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 25,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 26,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 27,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 28,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 29,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 30,
"capture": 0,
"items": [
{
"id": "bookmaker3",
"type": "world_song"
}
]
}
]
}

View File

@@ -1,51 +1,51 @@
{
"map_id": "byd_dement",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "dement2",
"require_type": "chart_unlock",
"coordinate": "-400,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [13, 1],
"affinity_multiplier": [2.6, 1.6],
"step_count": 4,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_dement",
"position": 0,
"capture": 100.2865329512894
}, {
"map_id": "byd_dement",
"position": 1,
"capture": 110.31518624641834,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_dement",
"position": 2,
"capture": 39.39828080229226,
"items": [{
"type": "fragment",
"amount": 1200
}]
}, {
"map_id": "byd_dement",
"position": 3,
"capture": 0,
"items": [{
"id": "dement3",
"type": "world_song"
}]
}]
{
"map_id": "byd_dement",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "dement2",
"require_type": "chart_unlock",
"coordinate": "-400,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [13, 1],
"affinity_multiplier": [2.6, 1.6],
"step_count": 4,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_dement",
"position": 0,
"capture": 100.2865329512894
}, {
"map_id": "byd_dement",
"position": 1,
"capture": 110.31518624641834,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_dement",
"position": 2,
"capture": 39.39828080229226,
"items": [{
"type": "fragment",
"amount": 1200
}]
}, {
"map_id": "byd_dement",
"position": 3,
"capture": 0,
"items": [{
"id": "dement3",
"type": "world_song"
}]
}]
}

View File

@@ -1,45 +1,45 @@
{
"map_id": "byd_fairytale",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "fairytale2",
"require_type": "chart_unlock",
"coordinate": "400,650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 3,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_fairytale",
"position": 0,
"capture": 100,
"step_type": ["speedlimit"],
"speed_limit_value": 65
}, {
"map_id": "byd_fairytale",
"position": 1,
"capture": 100,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 4
}]
}, {
"map_id": "byd_fairytale",
"position": 2,
"capture": 0,
"items": [{
"id": "fairytale3",
"type": "world_song"
}]
}]
{
"map_id": "byd_fairytale",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "fairytale2",
"require_type": "chart_unlock",
"coordinate": "400,650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 3,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_fairytale",
"position": 0,
"capture": 100,
"step_type": ["speedlimit"],
"speed_limit_value": 65
}, {
"map_id": "byd_fairytale",
"position": 1,
"capture": 100,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 4
}]
}, {
"map_id": "byd_fairytale",
"position": 2,
"capture": 0,
"items": [{
"id": "fairytale3",
"type": "world_song"
}]
}]
}

View File

@@ -1,68 +1,68 @@
{
"map_id": "byd_goodtek",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "goodtek2",
"require_type": "chart_unlock",
"coordinate": "900,650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 6,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_goodtek",
"position": 0,
"capture": 50
}, {
"map_id": "byd_goodtek",
"position": 1,
"capture": 50,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_goodtek",
"position": 2,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 250
}]
}, {
"map_id": "byd_goodtek",
"position": 3,
"capture": 50,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_goodtek",
"position": 4,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 275
}]
}, {
"map_id": "byd_goodtek",
"position": 5,
"capture": 0,
"items": [{
"id": "goodtek3",
"type": "world_song"
}]
}]
{
"map_id": "byd_goodtek",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "goodtek2",
"require_type": "chart_unlock",
"coordinate": "900,650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 6,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_goodtek",
"position": 0,
"capture": 50
}, {
"map_id": "byd_goodtek",
"position": 1,
"capture": 50,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_goodtek",
"position": 2,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 250
}]
}, {
"map_id": "byd_goodtek",
"position": 3,
"capture": 50,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_goodtek",
"position": 4,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 275
}]
}, {
"map_id": "byd_goodtek",
"position": 5,
"capture": 0,
"items": [{
"id": "goodtek3",
"type": "world_song"
}]
}]
}

View File

@@ -1,91 +1,91 @@
{
"map_id": "byd_infinityheaven",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "infinityheaven2",
"require_type": "chart_unlock",
"coordinate": "650,900",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 9,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_infinityheaven",
"position": 0,
"capture": 25
}, {
"map_id": "byd_infinityheaven",
"position": 1,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 2,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 3,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 4,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 2
}]
}, {
"map_id": "byd_infinityheaven",
"position": 5,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 6,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 7,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 8,
"capture": 0,
"items": [{
"id": "infinityheaven3",
"type": "world_song"
}]
}]
{
"map_id": "byd_infinityheaven",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "infinityheaven2",
"require_type": "chart_unlock",
"coordinate": "650,900",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 9,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_infinityheaven",
"position": 0,
"capture": 25
}, {
"map_id": "byd_infinityheaven",
"position": 1,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 2,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 3,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 4,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 2
}]
}, {
"map_id": "byd_infinityheaven",
"position": 5,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 6,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 7,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"map_id": "byd_infinityheaven",
"position": 8,
"capture": 0,
"items": [{
"id": "infinityheaven3",
"type": "world_song"
}]
}]
}

View File

@@ -0,0 +1,78 @@
{
"map_id": "byd_lostcivilization",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "lostcivilization2",
"require_type": "chart_unlock",
"coordinate": "-650,-900",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [0, 1],
"affinity_multiplier": [1.9, 1.9],
"step_count": 7,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_lostcivilization",
"position": 0,
"capture": 85
}, {
"map_id": "byd_lostcivilization",
"position": 1,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 2,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 3,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 4,
"capture": 100,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 5,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 525
}]
}, {
"map_id": "byd_lostcivilization",
"position": 6,
"capture": 0,
"items": [{
"id": "lostcivilization3",
"type": "world_song"
}]
}]
}

View File

@@ -0,0 +1,70 @@
{
"map_id": "byd_lumia",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "lumia2",
"require_type": "chart_unlock",
"coordinate": "650,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 150,
"character_affinity": [0, 15, 14, 16],
"affinity_multiplier": [2.4, 1.9, 2.4, 2.9],
"step_count": 6,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_lumia",
"position": 0,
"capture": 10,
"restrict_id": "core",
"restrict_type": "pack_id"
}, {
"map_id": "byd_lumia",
"position": 1,
"capture": 20,
"items": [{
"type": "fragment",
"amount": 200
}]
}, {
"map_id": "byd_lumia",
"position": 2,
"capture": 30,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lumia",
"position": 3,
"capture": 40,
"items": [{
"type": "fragment",
"amount": 250
}]
}, {
"map_id": "byd_lumia",
"position": 4,
"capture": 50,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lumia",
"position": 5,
"capture": 0,
"items": [{
"id": "lumia3",
"type": "world_song"
}]
}]
}

View File

@@ -1,61 +1,61 @@
{
"map_id": "byd_purgatorium",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "purgatorium2",
"require_type": "chart_unlock",
"coordinate": "-900,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [13, 1],
"affinity_multiplier": [2.6, 1.6],
"step_count": 5,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_purgatorium",
"position": 0,
"capture": 75
}, {
"map_id": "byd_purgatorium",
"position": 1,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 2,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 3,
"capture": 75,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 4,
"capture": 0,
"items": [{
"id": "purgatorium3",
"type": "world_song"
}]
}]
{
"map_id": "byd_purgatorium",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "purgatorium2",
"require_type": "chart_unlock",
"coordinate": "-900,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 200,
"character_affinity": [13, 1],
"affinity_multiplier": [2.6, 1.6],
"step_count": 5,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_purgatorium",
"position": 0,
"capture": 75
}, {
"map_id": "byd_purgatorium",
"position": 1,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 2,
"capture": 25,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 3,
"capture": 75,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_purgatorium",
"position": 4,
"capture": 0,
"items": [{
"id": "purgatorium3",
"type": "world_song"
}]
}]
}

View File

@@ -1,42 +1,42 @@
{
"map_id": "byd_vexaria",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "vexaria2",
"require_type": "chart_unlock",
"coordinate": "650,400",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 150,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 3,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_vexaria",
"position": 0,
"capture": 140
}, {
"map_id": "byd_vexaria",
"position": 1,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 616
}]
}, {
"map_id": "byd_vexaria",
"position": 2,
"capture": 0,
"items": [{
"id": "vexaria3",
"type": "world_song"
}]
}]
{
"map_id": "byd_vexaria",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "vexaria2",
"require_type": "chart_unlock",
"coordinate": "650,400",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 150,
"character_affinity": [0, 1],
"affinity_multiplier": [2.5, 1.5],
"step_count": 3,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_vexaria",
"position": 0,
"capture": 140
}, {
"map_id": "byd_vexaria",
"position": 1,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 616
}]
}, {
"map_id": "byd_vexaria",
"position": 2,
"capture": 0,
"items": [{
"id": "vexaria3",
"type": "world_song"
}]
}]
}

View File

@@ -1,428 +1,428 @@
{
"map_id": "chapter3_conflict",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 3,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-360,0",
"step_count": 85,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10
}, {
"position": 2,
"capture": 10
}, {
"position": 3,
"capture": 10
}, {
"position": 4,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 5,
"capture": 1,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 6,
"capture": 1
}, {
"position": 7,
"capture": 1,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 1,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 9,
"capture": 1
}, {
"position": 10,
"capture": 20,
"items": [{
"type": "fragment",
"amount": 10
}]
}, {
"position": 11,
"capture": 20
}, {
"position": 12,
"capture": 20
}, {
"position": 13,
"capture": 20
}, {
"position": 14,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 15,
"capture": 2,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"position": 16,
"capture": 2
}, {
"position": 17,
"capture": 2,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 18,
"capture": 2,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 19,
"capture": 2,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 20,
"capture": 30,
"items": [{
"type": "fragment",
"amount": 15
}]
}, {
"position": 21,
"capture": 30
}, {
"position": 22,
"capture": 30
}, {
"position": 23,
"capture": 30
}, {
"position": 24,
"capture": 30,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 25,
"capture": 3,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 26,
"capture": 3
}, {
"position": 27,
"capture": 3,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 28,
"capture": 3,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 29,
"capture": 3
}, {
"position": 30,
"capture": 40,
"items": [{
"id": "bookmaker",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 40
}, {
"position": 32,
"capture": 40
}, {
"position": 33,
"capture": 40
}, {
"position": 34,
"capture": 40,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 35,
"capture": 4,
"items": [{
"type": "fragment",
"amount": 200
}]
}, {
"position": 36,
"capture": 4
}, {
"position": 37,
"capture": 4,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 38,
"capture": 4,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 39,
"capture": 4,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 2
}]
}, {
"position": 40,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 50
}, {
"position": 42,
"capture": 50
}, {
"position": 43,
"capture": 50
}, {
"position": 44,
"capture": 50,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 45,
"capture": 5,
"items": [{
"type": "fragment",
"amount": 250
}]
}, {
"position": 46,
"capture": 5
}, {
"position": 47,
"capture": 5,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 48,
"capture": 5,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 49,
"capture": 5,
"items": [{
"id": "darakunosono",
"type": "world_song"
}]
}, {
"position": 50,
"capture": 60,
"items": [{
"type": "fragment",
"amount": 30
}]
}, {
"position": 51,
"capture": 60
}, {
"position": 52,
"capture": 60
}, {
"position": 53,
"capture": 60
}, {
"position": 54,
"capture": 60,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 55,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 300
}]
}, {
"position": 56,
"capture": 6
}, {
"position": 57,
"capture": 6,
"restrict_id": "darakunosono",
"restrict_type": "song_id"
}, {
"position": 58,
"capture": 6,
"restrict_id": "darakunosono",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 3
}]
}, {
"position": 60,
"capture": 70,
"items": [{
"type": "fragment",
"amount": 40
}]
}, {
"position": 61,
"capture": 70
}, {
"position": 62,
"capture": 70,
"items": [{
"id": "espebranch",
"type": "world_song"
}]
}, {
"position": 63,
"capture": 70
}, {
"position": 64,
"capture": 70,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 65,
"capture": 8
}, {
"position": 66,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 67,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 68,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 69,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 70,
"capture": 80,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 71,
"capture": 80
}, {
"position": 72,
"capture": 80
}, {
"position": 73,
"capture": 80
}, {
"position": 74,
"capture": 80,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 75,
"capture": 9,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 76,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 77,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 78,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 79,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 90
}, {
"position": 81,
"capture": 90
}, {
"position": 82,
"capture": 90
}, {
"position": 83,
"capture": 90
}, {
"position": 84,
"capture": 0,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}]
{
"map_id": "chapter3_conflict",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 3,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-360,0",
"step_count": 85,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10
}, {
"position": 2,
"capture": 10
}, {
"position": 3,
"capture": 10
}, {
"position": 4,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 5,
"capture": 1,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 6,
"capture": 1
}, {
"position": 7,
"capture": 1,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 1,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 9,
"capture": 1
}, {
"position": 10,
"capture": 20,
"items": [{
"type": "fragment",
"amount": 10
}]
}, {
"position": 11,
"capture": 20
}, {
"position": 12,
"capture": 20
}, {
"position": 13,
"capture": 20
}, {
"position": 14,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 15,
"capture": 2,
"items": [{
"type": "fragment",
"amount": 100
}]
}, {
"position": 16,
"capture": 2
}, {
"position": 17,
"capture": 2,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 18,
"capture": 2,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 19,
"capture": 2,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 20,
"capture": 30,
"items": [{
"type": "fragment",
"amount": 15
}]
}, {
"position": 21,
"capture": 30
}, {
"position": 22,
"capture": 30
}, {
"position": 23,
"capture": 30
}, {
"position": 24,
"capture": 30,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 25,
"capture": 3,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 26,
"capture": 3
}, {
"position": 27,
"capture": 3,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 28,
"capture": 3,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 29,
"capture": 3
}, {
"position": 30,
"capture": 40,
"items": [{
"id": "bookmaker",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 40
}, {
"position": 32,
"capture": 40
}, {
"position": 33,
"capture": 40
}, {
"position": 34,
"capture": 40,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 35,
"capture": 4,
"items": [{
"type": "fragment",
"amount": 200
}]
}, {
"position": 36,
"capture": 4
}, {
"position": 37,
"capture": 4,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 38,
"capture": 4,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 39,
"capture": 4,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 2
}]
}, {
"position": 40,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 50
}, {
"position": 42,
"capture": 50
}, {
"position": 43,
"capture": 50
}, {
"position": 44,
"capture": 50,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 45,
"capture": 5,
"items": [{
"type": "fragment",
"amount": 250
}]
}, {
"position": 46,
"capture": 5
}, {
"position": 47,
"capture": 5,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 48,
"capture": 5,
"restrict_id": "bookmaker",
"restrict_type": "song_id"
}, {
"position": 49,
"capture": 5,
"items": [{
"id": "darakunosono",
"type": "world_song"
}]
}, {
"position": 50,
"capture": 60,
"items": [{
"type": "fragment",
"amount": 30
}]
}, {
"position": 51,
"capture": 60
}, {
"position": 52,
"capture": 60
}, {
"position": 53,
"capture": 60
}, {
"position": 54,
"capture": 60,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 55,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 300
}]
}, {
"position": 56,
"capture": 6
}, {
"position": 57,
"capture": 6,
"restrict_id": "darakunosono",
"restrict_type": "song_id"
}, {
"position": 58,
"capture": 6,
"restrict_id": "darakunosono",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 3
}]
}, {
"position": 60,
"capture": 70,
"items": [{
"type": "fragment",
"amount": 40
}]
}, {
"position": 61,
"capture": 70
}, {
"position": 62,
"capture": 70,
"items": [{
"id": "espebranch",
"type": "world_song"
}]
}, {
"position": 63,
"capture": 70
}, {
"position": 64,
"capture": 70,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 65,
"capture": 8
}, {
"position": 66,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 67,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 68,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 69,
"capture": 8,
"restrict_id": "suomi",
"restrict_type": "song_id"
}, {
"position": 70,
"capture": 80,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 71,
"capture": 80
}, {
"position": 72,
"capture": 80
}, {
"position": 73,
"capture": 80
}, {
"position": 74,
"capture": 80,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 75,
"capture": 9,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 76,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 77,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 78,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 79,
"capture": 9,
"restrict_id": "rugie",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 90
}, {
"position": 81,
"capture": 90
}, {
"position": 82,
"capture": 90
}, {
"position": 83,
"capture": 90
}, {
"position": 84,
"capture": 0,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}]
}

View File

@@ -1,268 +1,268 @@
{
"map_id": "chapter3_conflict_2",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 3,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-240,-180",
"step_count": 51,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 2,
"capture": 10
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 6,
"capture": 10
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10
}, {
"position": 11,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 12,
"capture": 10
}, {
"position": 13,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 14,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 15,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 16,
"capture": 10
}, {
"position": 17,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 18,
"capture": 10
}, {
"position": 19,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 20,
"capture": 10
}, {
"position": 21,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 22,
"capture": 10
}, {
"position": 23,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 24,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 25,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 26,
"capture": 10
}, {
"position": 27,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 28,
"capture": 10
}, {
"position": 29,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 30,
"capture": 10
}, {
"position": 31,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 32,
"capture": 10
}, {
"position": 33,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 34,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 35,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 36,
"capture": 10
}, {
"position": 37,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 38,
"capture": 10
}, {
"position": 39,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 40,
"capture": 10,
"items": [{
"id": "nhelv",
"type": "world_song"
}]
}, {
"position": 41,
"capture": 100
}, {
"position": 42,
"capture": 100
}, {
"position": 43,
"capture": 100,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 44,
"capture": 100
}, {
"position": 45,
"capture": 100,
"items": [{
"type": "core",
"id": "core_ambivalent",
"amount": 5
}]
}, {
"position": 46,
"capture": 100
}, {
"position": 47,
"capture": 100,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 48,
"capture": 100
}, {
"position": 49,
"capture": 20,
"restrict_id": "nhelv",
"restrict_type": "song_id"
}, {
"position": 50,
"capture": 0,
"items": [{
"type": "core",
"id": "core_ambivalent",
"amount": 5
}]
}]
{
"map_id": "chapter3_conflict_2",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 3,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-240,-180",
"step_count": 51,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 2,
"capture": 10
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 6,
"capture": 10
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10
}, {
"position": 11,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 12,
"capture": 10
}, {
"position": 13,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 14,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 15,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 16,
"capture": 10
}, {
"position": 17,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 18,
"capture": 10
}, {
"position": 19,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 20,
"capture": 10
}, {
"position": 21,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 22,
"capture": 10
}, {
"position": 23,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 24,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 25,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 26,
"capture": 10
}, {
"position": 27,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 28,
"capture": 10
}, {
"position": 29,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 30,
"capture": 10
}, {
"position": 31,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 32,
"capture": 10
}, {
"position": 33,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 34,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 35,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 36,
"capture": 10
}, {
"position": 37,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 38,
"capture": 10
}, {
"position": 39,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 40,
"capture": 10,
"items": [{
"id": "nhelv",
"type": "world_song"
}]
}, {
"position": 41,
"capture": 100
}, {
"position": 42,
"capture": 100
}, {
"position": 43,
"capture": 100,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 44,
"capture": 100
}, {
"position": 45,
"capture": 100,
"items": [{
"type": "core",
"id": "core_ambivalent",
"amount": 5
}]
}, {
"position": 46,
"capture": 100
}, {
"position": 47,
"capture": 100,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 48,
"capture": 100
}, {
"position": 49,
"capture": 20,
"restrict_id": "nhelv",
"restrict_type": "song_id"
}, {
"position": 50,
"capture": 0,
"items": [{
"type": "core",
"id": "core_ambivalent",
"amount": 5
}]
}]
}

View File

@@ -1,236 +1,236 @@
{
"map_id": "chapter4_ripples",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 4,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-280,-200",
"step_count": 51,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 13
}, {
"position": 1,
"capture": 14
}, {
"position": 2,
"capture": 15
}, {
"position": 3,
"capture": 16
}, {
"position": 4,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 170
}]
}, {
"position": 5,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 6,
"capture": 15,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 7,
"capture": 14,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 13
}, {
"position": 9,
"capture": 14
}, {
"position": 10,
"capture": 15
}, {
"position": 11,
"capture": 16
}, {
"position": 12,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 170
}]
}, {
"position": 13,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 14,
"capture": 15,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 15,
"capture": 14,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 16,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 17,
"capture": 14
}, {
"position": 18,
"capture": 15
}, {
"position": 19,
"capture": 16
}, {
"position": 20,
"capture": 17,
"items": [{
"id": "grimheart",
"type": "world_song"
}]
}, {
"position": 21,
"capture": 16
}, {
"position": 22,
"capture": 15
}, {
"position": 23,
"capture": 16
}, {
"position": 24,
"capture": 17
}, {
"position": 25,
"capture": 18
}, {
"position": 26,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 190
}]
}, {
"position": 27,
"capture": 18,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 28,
"capture": 17,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 16,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 15
}, {
"position": 31,
"capture": 16
}, {
"position": 32,
"capture": 17
}, {
"position": 33,
"capture": 18
}, {
"position": 34,
"capture": 19,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 35,
"capture": 18
}, {
"position": 36,
"capture": 17
}, {
"position": 37,
"capture": 16
}, {
"position": 38,
"capture": 15
}, {
"position": 39,
"capture": 16
}, {
"position": 40,
"capture": 17
}, {
"position": 41,
"capture": 18
}, {
"position": 42,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 190
}]
}, {
"position": 43,
"capture": 18,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 17,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 16,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 46,
"capture": 15,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 47,
"capture": 16
}, {
"position": 48,
"capture": 17
}, {
"position": 49,
"capture": 18
}, {
"position": 50,
"capture": 0,
"items": [{
"id": "vector",
"type": "world_song"
}]
}]
{
"map_id": "chapter4_ripples",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 4,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-280,-200",
"step_count": 51,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 13
}, {
"position": 1,
"capture": 14
}, {
"position": 2,
"capture": 15
}, {
"position": 3,
"capture": 16
}, {
"position": 4,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 170
}]
}, {
"position": 5,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 6,
"capture": 15,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 7,
"capture": 14,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 13
}, {
"position": 9,
"capture": 14
}, {
"position": 10,
"capture": 15
}, {
"position": 11,
"capture": 16
}, {
"position": 12,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 170
}]
}, {
"position": 13,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 14,
"capture": 15,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 15,
"capture": 14,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 16,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 17,
"capture": 14
}, {
"position": 18,
"capture": 15
}, {
"position": 19,
"capture": 16
}, {
"position": 20,
"capture": 17,
"items": [{
"id": "grimheart",
"type": "world_song"
}]
}, {
"position": 21,
"capture": 16
}, {
"position": 22,
"capture": 15
}, {
"position": 23,
"capture": 16
}, {
"position": 24,
"capture": 17
}, {
"position": 25,
"capture": 18
}, {
"position": 26,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 190
}]
}, {
"position": 27,
"capture": 18,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 28,
"capture": 17,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 16,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 15
}, {
"position": 31,
"capture": 16
}, {
"position": 32,
"capture": 17
}, {
"position": 33,
"capture": 18
}, {
"position": 34,
"capture": 19,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 35,
"capture": 18
}, {
"position": 36,
"capture": 17
}, {
"position": 37,
"capture": 16
}, {
"position": 38,
"capture": 15
}, {
"position": 39,
"capture": 16
}, {
"position": 40,
"capture": 17
}, {
"position": 41,
"capture": 18
}, {
"position": 42,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 190
}]
}, {
"position": 43,
"capture": 18,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 17,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 16,
"restrict_id": "grimheart",
"restrict_type": "song_id"
}, {
"position": 46,
"capture": 15,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 47,
"capture": 16
}, {
"position": 48,
"capture": 17
}, {
"position": 49,
"capture": 18
}, {
"position": 50,
"capture": 0,
"items": [{
"id": "vector",
"type": "world_song"
}]
}]
}

View File

@@ -1,199 +1,199 @@
{
"map_id": "chapter4_waves",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 4,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-480,-200",
"step_count": 41,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 11
}, {
"position": 2,
"capture": 13
}, {
"position": 3,
"capture": 16
}, {
"position": 4,
"capture": 20
}, {
"position": 5,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 6,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 7,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 9,
"capture": 11
}, {
"position": 10,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 11,
"capture": 11
}, {
"position": 12,
"capture": 13
}, {
"position": 13,
"capture": 16
}, {
"position": 14,
"capture": 20
}, {
"position": 15,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 16,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 17,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 18,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 19,
"capture": 11
}, {
"position": 20,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 21,
"capture": 11
}, {
"position": 22,
"capture": 13
}, {
"position": 23,
"capture": 16
}, {
"position": 24,
"capture": 20
}, {
"position": 25,
"capture": 25,
"items": [{
"id": "revixy",
"type": "world_song"
}]
}, {
"position": 26,
"capture": 26
}, {
"position": 27,
"capture": 28
}, {
"position": 28,
"capture": 31
}, {
"position": 29,
"capture": 35
}, {
"position": 30,
"capture": 40,
"items": [{
"type": "fragment",
"amount": 400
}]
}, {
"position": 31,
"capture": 35,
"restrict_id": "revixy",
"restrict_type": "song_id"
}, {
"position": 32,
"capture": 31,
"restrict_id": "revixy",
"restrict_type": "song_id"
}, {
"position": 33,
"capture": 28,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 34,
"capture": 26
}, {
"position": 35,
"capture": 25,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 36,
"capture": 26
}, {
"position": 37,
"capture": 28
}, {
"position": 38,
"capture": 31
}, {
"position": 39,
"capture": 35
}, {
"position": 40,
"capture": 0,
"items": [{
"id": "supernova",
"type": "world_song"
}]
}]
{
"map_id": "chapter4_waves",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 4,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "fragment",
"require_value": 200,
"coordinate": "-480,-200",
"step_count": 41,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 11
}, {
"position": 2,
"capture": 13
}, {
"position": 3,
"capture": 16
}, {
"position": 4,
"capture": 20
}, {
"position": 5,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 6,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 7,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 8,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 9,
"capture": 11
}, {
"position": 10,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 11,
"capture": 11
}, {
"position": 12,
"capture": 13
}, {
"position": 13,
"capture": 16
}, {
"position": 14,
"capture": 20
}, {
"position": 15,
"capture": 25,
"items": [{
"type": "fragment",
"amount": 150
}]
}, {
"position": 16,
"capture": 20,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 17,
"capture": 16,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 18,
"capture": 13,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 19,
"capture": 11
}, {
"position": 20,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 21,
"capture": 11
}, {
"position": 22,
"capture": 13
}, {
"position": 23,
"capture": 16
}, {
"position": 24,
"capture": 20
}, {
"position": 25,
"capture": 25,
"items": [{
"id": "revixy",
"type": "world_song"
}]
}, {
"position": 26,
"capture": 26
}, {
"position": 27,
"capture": 28
}, {
"position": 28,
"capture": 31
}, {
"position": 29,
"capture": 35
}, {
"position": 30,
"capture": 40,
"items": [{
"type": "fragment",
"amount": 400
}]
}, {
"position": 31,
"capture": 35,
"restrict_id": "revixy",
"restrict_type": "song_id"
}, {
"position": 32,
"capture": 31,
"restrict_id": "revixy",
"restrict_type": "song_id"
}, {
"position": 33,
"capture": 28,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 34,
"capture": 26
}, {
"position": 35,
"capture": 25,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 36,
"capture": 26
}, {
"position": 37,
"capture": 28
}, {
"position": 38,
"capture": 31
}, {
"position": 39,
"capture": 35
}, {
"position": 40,
"capture": 0,
"items": [{
"id": "supernova",
"type": "world_song"
}]
}]
}

View File

@@ -1,477 +1,477 @@
{
"map_id": "extra_originals",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 2,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"coordinate": "0,-200",
"step_count": 101,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 2
}, {
"position": 1,
"capture": 3
}, {
"position": 2,
"capture": 4
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 6,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 5,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 6,
"capture": 8
}, {
"position": 7,
"capture": 9
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 11,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 10,
"capture": 12,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 11,
"capture": 13
}, {
"position": 12,
"capture": 14
}, {
"position": 13,
"capture": 15
}, {
"position": 14,
"capture": 16,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 16,
"capture": 16,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 17,
"capture": 15
}, {
"position": 18,
"capture": 14
}, {
"position": 19,
"capture": 13
}, {
"position": 20,
"capture": 12,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 21,
"capture": 11,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 22,
"capture": 10
}, {
"position": 23,
"capture": 9
}, {
"position": 24,
"capture": 8
}, {
"position": 25,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 26,
"capture": 6,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 27,
"capture": 5
}, {
"position": 28,
"capture": 4
}, {
"position": 29,
"capture": 3,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 30,
"capture": 4,
"items": [{
"id": "syro",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 32,
"capture": 6
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 8,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 35,
"capture": 9,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 36,
"capture": 10
}, {
"position": 37,
"capture": 11
}, {
"position": 38,
"capture": 12
}, {
"position": 39,
"capture": 13,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 40,
"capture": 14,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 41,
"capture": 15
}, {
"position": 42,
"capture": 16
}, {
"position": 43,
"capture": 17
}, {
"position": 44,
"capture": 18,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 46,
"capture": 18,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 47,
"capture": 17
}, {
"position": 48,
"capture": 16
}, {
"position": 49,
"capture": 15
}, {
"position": 50,
"capture": 14,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 51,
"capture": 13,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 52,
"capture": 12
}, {
"position": 53,
"capture": 11
}, {
"position": 54,
"capture": 10
}, {
"position": 55,
"capture": 9,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 56,
"capture": 8,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 6
}, {
"position": 59,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 60,
"capture": 6,
"items": [{
"id": "blaster",
"type": "world_song"
}]
}, {
"position": 61,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 62,
"capture": 8
}, {
"position": 63,
"capture": 9
}, {
"position": 64,
"capture": 10,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 65,
"capture": 11,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 66,
"capture": 12
}, {
"position": 67,
"capture": 13
}, {
"position": 68,
"capture": 14
}, {
"position": 69,
"capture": 15,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 70,
"capture": 16,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 71,
"capture": 17
}, {
"position": 72,
"capture": 18
}, {
"position": 73,
"capture": 19
}, {
"position": 74,
"capture": 20,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 21,
"items": [{
"type": "fragment",
"amount": 90
}]
}, {
"position": 76,
"capture": 22
}, {
"position": 77,
"capture": 23
}, {
"position": 78,
"capture": 23
}, {
"position": 79,
"capture": 22
}, {
"position": 80,
"capture": 21,
"items": [{
"type": "fragment",
"amount": 90
}]
}, {
"position": 81,
"capture": 20,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 82,
"capture": 19
}, {
"position": 83,
"capture": 18
}, {
"position": 84,
"capture": 17
}, {
"position": 85,
"capture": 16,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 86,
"capture": 15,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 87,
"capture": 14
}, {
"position": 88,
"capture": 13
}, {
"position": 89,
"capture": 12
}, {
"position": 90,
"capture": 11,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 91,
"capture": 10,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 92,
"capture": 9
}, {
"position": 93,
"capture": 8
}, {
"position": 94,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 95,
"capture": 8,
"items": [{
"id": "cyberneciacatharsis",
"type": "world_song"
}]
}, {
"position": 96,
"capture": 400
}, {
"position": 97,
"capture": 2,
"step_type": ["plusstamina"],
"plus_stamina_value": 6
}, {
"position": 98,
"capture": 450,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 5
}]
}, {
"position": 99,
"capture": 1,
"step_type": ["plusstamina"],
"plus_stamina_value": 6
}, {
"position": 100,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 5
}]
}]
{
"map_id": "extra_originals",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 2,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"coordinate": "0,-200",
"step_count": 101,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 2
}, {
"position": 1,
"capture": 3
}, {
"position": 2,
"capture": 4
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 6,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 5,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 6,
"capture": 8
}, {
"position": 7,
"capture": 9
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 11,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 10,
"capture": 12,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 11,
"capture": 13
}, {
"position": 12,
"capture": 14
}, {
"position": 13,
"capture": 15
}, {
"position": 14,
"capture": 16,
"restrict_id": "infinityheaven",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 17,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 16,
"capture": 16,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 17,
"capture": 15
}, {
"position": 18,
"capture": 14
}, {
"position": 19,
"capture": 13
}, {
"position": 20,
"capture": 12,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 21,
"capture": 11,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 22,
"capture": 10
}, {
"position": 23,
"capture": 9
}, {
"position": 24,
"capture": 8
}, {
"position": 25,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 26,
"capture": 6,
"restrict_id": "reinvent",
"restrict_type": "song_id"
}, {
"position": 27,
"capture": 5
}, {
"position": 28,
"capture": 4
}, {
"position": 29,
"capture": 3,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 30,
"capture": 4,
"items": [{
"id": "syro",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 32,
"capture": 6
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 8,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 35,
"capture": 9,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 36,
"capture": 10
}, {
"position": 37,
"capture": 11
}, {
"position": 38,
"capture": 12
}, {
"position": 39,
"capture": 13,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 40,
"capture": 14,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 41,
"capture": 15
}, {
"position": 42,
"capture": 16
}, {
"position": 43,
"capture": 17
}, {
"position": 44,
"capture": 18,
"restrict_id": "dement",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 19,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 46,
"capture": 18,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 47,
"capture": 17
}, {
"position": 48,
"capture": 16
}, {
"position": 49,
"capture": 15
}, {
"position": 50,
"capture": 14,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 51,
"capture": 13,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 52,
"capture": 12
}, {
"position": 53,
"capture": 11
}, {
"position": 54,
"capture": 10
}, {
"position": 55,
"capture": 9,
"items": [{
"type": "fragment",
"amount": 60
}]
}, {
"position": 56,
"capture": 8,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 6
}, {
"position": 59,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 60,
"capture": 6,
"items": [{
"id": "blaster",
"type": "world_song"
}]
}, {
"position": 61,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 62,
"capture": 8
}, {
"position": 63,
"capture": 9
}, {
"position": 64,
"capture": 10,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 65,
"capture": 11,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 66,
"capture": 12
}, {
"position": 67,
"capture": 13
}, {
"position": 68,
"capture": 14
}, {
"position": 69,
"capture": 15,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 70,
"capture": 16,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 71,
"capture": 17
}, {
"position": 72,
"capture": 18
}, {
"position": 73,
"capture": 19
}, {
"position": 74,
"capture": 20,
"restrict_id": "syro",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 21,
"items": [{
"type": "fragment",
"amount": 90
}]
}, {
"position": 76,
"capture": 22
}, {
"position": 77,
"capture": 23
}, {
"position": 78,
"capture": 23
}, {
"position": 79,
"capture": 22
}, {
"position": 80,
"capture": 21,
"items": [{
"type": "fragment",
"amount": 90
}]
}, {
"position": 81,
"capture": 20,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 82,
"capture": 19
}, {
"position": 83,
"capture": 18
}, {
"position": 84,
"capture": 17
}, {
"position": 85,
"capture": 16,
"items": [{
"type": "fragment",
"amount": 80
}]
}, {
"position": 86,
"capture": 15,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 87,
"capture": 14
}, {
"position": 88,
"capture": 13
}, {
"position": 89,
"capture": 12
}, {
"position": 90,
"capture": 11,
"items": [{
"type": "fragment",
"amount": 70
}]
}, {
"position": 91,
"capture": 10,
"restrict_id": "blaster",
"restrict_type": "song_id"
}, {
"position": 92,
"capture": 9
}, {
"position": 93,
"capture": 8
}, {
"position": 94,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 95,
"capture": 8,
"items": [{
"id": "cyberneciacatharsis",
"type": "world_song"
}]
}, {
"position": 96,
"capture": 400
}, {
"position": 97,
"capture": 2,
"step_type": ["plusstamina"],
"plus_stamina_value": 6
}, {
"position": 98,
"capture": 450,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 5
}]
}, {
"position": 99,
"capture": 1,
"step_type": ["plusstamina"],
"plus_stamina_value": 6
}, {
"position": 100,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 5
}]
}]
}

View File

@@ -1,407 +1,407 @@
{
"map_id": "hikari_art",
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 1,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"is_legacy": true,
"stamina_cost": 1,
"coordinate": "-270,150",
"step_count": 91,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 5
}, {
"position": 1,
"capture": 5
}, {
"position": 2,
"capture": 5
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 5
}, {
"position": 5,
"capture": 6,
"items": [{
"id": "babaroque",
"type": "world_song"
}]
}, {
"position": 6,
"capture": 6
}, {
"position": 7,
"capture": 6
}, {
"position": 8,
"capture": 6
}, {
"position": 9,
"capture": 6
}, {
"position": 10,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 11,
"capture": 6
}, {
"position": 12,
"capture": 6
}, {
"position": 13,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 14,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 16,
"capture": 6
}, {
"position": 17,
"capture": 6
}, {
"position": 18,
"capture": 6
}, {
"position": 19,
"capture": 6
}, {
"position": 20,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 21,
"capture": 6
}, {
"position": 22,
"capture": 6
}, {
"position": 23,
"capture": 6
}, {
"position": 24,
"capture": 6
}, {
"position": 25,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 26,
"capture": 6
}, {
"position": 27,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 28,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 7,
"items": [{
"id": "shadesoflight",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 7
}, {
"position": 32,
"capture": 7
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 7
}, {
"position": 35,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 36,
"capture": 7
}, {
"position": 37,
"capture": 7
}, {
"position": 38,
"capture": 7
}, {
"position": 39,
"capture": 7
}, {
"position": 40,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 7
}, {
"position": 42,
"capture": 7
}, {
"position": 43,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 46,
"capture": 7
}, {
"position": 47,
"capture": 7
}, {
"position": 48,
"capture": 7
}, {
"position": 49,
"capture": 7
}, {
"position": 50,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 51,
"capture": 7
}, {
"position": 52,
"capture": 7
}, {
"position": 53,
"capture": 7
}, {
"position": 54,
"capture": 7
}, {
"position": 55,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 56,
"capture": 7
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 60,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 61,
"capture": 7
}, {
"position": 62,
"capture": 7
}, {
"position": 63,
"capture": 7
}, {
"position": 64,
"capture": 7
}, {
"position": 65,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 66,
"capture": 7
}, {
"position": 67,
"capture": 7
}, {
"position": 68,
"capture": 7
}, {
"position": 69,
"capture": 7
}, {
"position": 70,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 71,
"capture": 7,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 72,
"capture": 7,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 73,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 74,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 100,
"items": [{
"id": "kanagawa",
"type": "world_song"
}]
}, {
"position": 76,
"capture": 100
}, {
"position": 77,
"capture": 100
}, {
"position": 78,
"capture": 100
}, {
"position": 79,
"capture": 100,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 100,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}, {
"position": 81,
"capture": 150
}, {
"position": 82,
"capture": 150
}, {
"position": 83,
"capture": 150
}, {
"position": 84,
"capture": 150,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 85,
"capture": 150,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}, {
"position": 86,
"capture": 200
}, {
"position": 87,
"capture": 200
}, {
"position": 88,
"capture": 200
}, {
"position": 89,
"capture": 200,
"restrict_id": "ignotus",
"restrict_type": "song_id"
}, {
"position": 90,
"capture": 0,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}]
{
"map_id": "hikari_art",
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 1,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"is_legacy": true,
"stamina_cost": 1,
"coordinate": "-270,150",
"step_count": 91,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 5
}, {
"position": 1,
"capture": 5
}, {
"position": 2,
"capture": 5
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 5
}, {
"position": 5,
"capture": 6,
"items": [{
"id": "babaroque",
"type": "world_song"
}]
}, {
"position": 6,
"capture": 6
}, {
"position": 7,
"capture": 6
}, {
"position": 8,
"capture": 6
}, {
"position": 9,
"capture": 6
}, {
"position": 10,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 11,
"capture": 6
}, {
"position": 12,
"capture": 6
}, {
"position": 13,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 14,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 16,
"capture": 6
}, {
"position": 17,
"capture": 6
}, {
"position": 18,
"capture": 6
}, {
"position": 19,
"capture": 6
}, {
"position": 20,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 21,
"capture": 6
}, {
"position": 22,
"capture": 6
}, {
"position": 23,
"capture": 6
}, {
"position": 24,
"capture": 6
}, {
"position": 25,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 26,
"capture": 6
}, {
"position": 27,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 28,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 6,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 7,
"items": [{
"id": "shadesoflight",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 7
}, {
"position": 32,
"capture": 7
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 7
}, {
"position": 35,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 36,
"capture": 7
}, {
"position": 37,
"capture": 7
}, {
"position": 38,
"capture": 7
}, {
"position": 39,
"capture": 7
}, {
"position": 40,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 7
}, {
"position": 42,
"capture": 7
}, {
"position": 43,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 46,
"capture": 7
}, {
"position": 47,
"capture": 7
}, {
"position": 48,
"capture": 7
}, {
"position": 49,
"capture": 7
}, {
"position": 50,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 51,
"capture": 7
}, {
"position": 52,
"capture": 7
}, {
"position": 53,
"capture": 7
}, {
"position": 54,
"capture": 7
}, {
"position": 55,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 56,
"capture": 7
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 60,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 61,
"capture": 7
}, {
"position": 62,
"capture": 7
}, {
"position": 63,
"capture": 7
}, {
"position": 64,
"capture": 7
}, {
"position": 65,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 66,
"capture": 7
}, {
"position": 67,
"capture": 7
}, {
"position": 68,
"capture": 7
}, {
"position": 69,
"capture": 7
}, {
"position": 70,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 71,
"capture": 7,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 72,
"capture": 7,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 73,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 74,
"capture": 7,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 100,
"items": [{
"id": "kanagawa",
"type": "world_song"
}]
}, {
"position": 76,
"capture": 100
}, {
"position": 77,
"capture": 100
}, {
"position": 78,
"capture": 100
}, {
"position": 79,
"capture": 100,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 100,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}, {
"position": 81,
"capture": 150
}, {
"position": 82,
"capture": 150
}, {
"position": 83,
"capture": 150
}, {
"position": 84,
"capture": 150,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 85,
"capture": 150,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}, {
"position": 86,
"capture": 200
}, {
"position": 87,
"capture": 200
}, {
"position": 88,
"capture": 200
}, {
"position": 89,
"capture": 200,
"restrict_id": "ignotus",
"restrict_type": "song_id"
}, {
"position": 90,
"capture": 0,
"items": [{
"type": "core",
"id": "core_hollow",
"amount": 5
}]
}]
}

View File

@@ -1,407 +1,407 @@
{
"map_id": "tairitsu_tech",
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 1,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"is_legacy": true,
"stamina_cost": 1,
"coordinate": "270,-150",
"step_count": 91,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 5
}, {
"position": 1,
"capture": 5
}, {
"position": 2,
"capture": 5
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 5
}, {
"position": 5,
"capture": 6,
"items": [{
"id": "lucifer",
"type": "world_song"
}]
}, {
"position": 6,
"capture": 6
}, {
"position": 7,
"capture": 6
}, {
"position": 8,
"capture": 6
}, {
"position": 9,
"capture": 6
}, {
"position": 10,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 11,
"capture": 6
}, {
"position": 12,
"capture": 6
}, {
"position": 13,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 14,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 16,
"capture": 6
}, {
"position": 17,
"capture": 6
}, {
"position": 18,
"capture": 6
}, {
"position": 19,
"capture": 6
}, {
"position": 20,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 21,
"capture": 6
}, {
"position": 22,
"capture": 6
}, {
"position": 23,
"capture": 6
}, {
"position": 24,
"capture": 6
}, {
"position": 25,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 26,
"capture": 6
}, {
"position": 27,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 28,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 7,
"items": [{
"id": "anokumene",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 7
}, {
"position": 32,
"capture": 7
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 7
}, {
"position": 35,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 36,
"capture": 7
}, {
"position": 37,
"capture": 7
}, {
"position": 38,
"capture": 7
}, {
"position": 39,
"capture": 7
}, {
"position": 40,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 7
}, {
"position": 42,
"capture": 7
}, {
"position": 43,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 46,
"capture": 7
}, {
"position": 47,
"capture": 7
}, {
"position": 48,
"capture": 7
}, {
"position": 49,
"capture": 7
}, {
"position": 50,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 51,
"capture": 7
}, {
"position": 52,
"capture": 7
}, {
"position": 53,
"capture": 7
}, {
"position": 54,
"capture": 7
}, {
"position": 55,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 56,
"capture": 7
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 60,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 61,
"capture": 7
}, {
"position": 62,
"capture": 7
}, {
"position": 63,
"capture": 7
}, {
"position": 64,
"capture": 7
}, {
"position": 65,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 66,
"capture": 7
}, {
"position": 67,
"capture": 7
}, {
"position": 68,
"capture": 7
}, {
"position": 69,
"capture": 7
}, {
"position": 70,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 71,
"capture": 7,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 72,
"capture": 7,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 73,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 74,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 100,
"items": [{
"id": "ignotus",
"type": "world_song"
}]
}, {
"position": 76,
"capture": 100
}, {
"position": 77,
"capture": 100
}, {
"position": 78,
"capture": 100
}, {
"position": 79,
"capture": 100,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 100,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 81,
"capture": 150
}, {
"position": 82,
"capture": 150
}, {
"position": 83,
"capture": 150
}, {
"position": 84,
"capture": 150,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 85,
"capture": 150,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 86,
"capture": 200
}, {
"position": 87,
"capture": 200
}, {
"position": 88,
"capture": 200
}, {
"position": 89,
"capture": 200,
"restrict_id": "kanagawa",
"restrict_type": "song_id"
}, {
"position": 90,
"capture": 0,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}]
{
"map_id": "tairitsu_tech",
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 1,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 1,
"is_legacy": true,
"stamina_cost": 1,
"coordinate": "270,-150",
"step_count": 91,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 5
}, {
"position": 1,
"capture": 5
}, {
"position": 2,
"capture": 5
}, {
"position": 3,
"capture": 5
}, {
"position": 4,
"capture": 5
}, {
"position": 5,
"capture": 6,
"items": [{
"id": "lucifer",
"type": "world_song"
}]
}, {
"position": 6,
"capture": 6
}, {
"position": 7,
"capture": 6
}, {
"position": 8,
"capture": 6
}, {
"position": 9,
"capture": 6
}, {
"position": 10,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 11,
"capture": 6
}, {
"position": 12,
"capture": 6
}, {
"position": 13,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 14,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 15,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 16,
"capture": 6
}, {
"position": 17,
"capture": 6
}, {
"position": 18,
"capture": 6
}, {
"position": 19,
"capture": 6
}, {
"position": 20,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 21,
"capture": 6
}, {
"position": 22,
"capture": 6
}, {
"position": 23,
"capture": 6
}, {
"position": 24,
"capture": 6
}, {
"position": 25,
"capture": 6,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 26,
"capture": 6
}, {
"position": 27,
"capture": 6,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 28,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 29,
"capture": 6,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 30,
"capture": 7,
"items": [{
"id": "anokumene",
"type": "world_song"
}]
}, {
"position": 31,
"capture": 7
}, {
"position": 32,
"capture": 7
}, {
"position": 33,
"capture": 7
}, {
"position": 34,
"capture": 7
}, {
"position": 35,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 36,
"capture": 7
}, {
"position": 37,
"capture": 7
}, {
"position": 38,
"capture": 7
}, {
"position": 39,
"capture": 7
}, {
"position": 40,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 41,
"capture": 7
}, {
"position": 42,
"capture": 7
}, {
"position": 43,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 44,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 45,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 46,
"capture": 7
}, {
"position": 47,
"capture": 7
}, {
"position": 48,
"capture": 7
}, {
"position": 49,
"capture": 7
}, {
"position": 50,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 51,
"capture": 7
}, {
"position": 52,
"capture": 7
}, {
"position": 53,
"capture": 7
}, {
"position": 54,
"capture": 7
}, {
"position": 55,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 56,
"capture": 7
}, {
"position": 57,
"capture": 7
}, {
"position": 58,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 59,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 60,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 50
}]
}, {
"position": 61,
"capture": 7
}, {
"position": 62,
"capture": 7
}, {
"position": 63,
"capture": 7
}, {
"position": 64,
"capture": 7
}, {
"position": 65,
"capture": 7,
"items": [{
"type": "fragment",
"amount": 25
}]
}, {
"position": 66,
"capture": 7
}, {
"position": 67,
"capture": 7
}, {
"position": 68,
"capture": 7
}, {
"position": 69,
"capture": 7
}, {
"position": 70,
"capture": 7,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 71,
"capture": 7,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 72,
"capture": 7,
"restrict_id": "lucifer",
"restrict_type": "song_id"
}, {
"position": 73,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 74,
"capture": 7,
"restrict_id": "anokumene",
"restrict_type": "song_id"
}, {
"position": 75,
"capture": 100,
"items": [{
"id": "ignotus",
"type": "world_song"
}]
}, {
"position": 76,
"capture": 100
}, {
"position": 77,
"capture": 100
}, {
"position": 78,
"capture": 100
}, {
"position": 79,
"capture": 100,
"restrict_id": "babaroque",
"restrict_type": "song_id"
}, {
"position": 80,
"capture": 100,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 81,
"capture": 150
}, {
"position": 82,
"capture": 150
}, {
"position": 83,
"capture": 150
}, {
"position": 84,
"capture": 150,
"restrict_id": "shadesoflight",
"restrict_type": "song_id"
}, {
"position": 85,
"capture": 150,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}, {
"position": 86,
"capture": 200
}, {
"position": 87,
"capture": 200
}, {
"position": 88,
"capture": 200
}, {
"position": 89,
"capture": 200,
"restrict_id": "kanagawa",
"restrict_type": "song_id"
}, {
"position": 90,
"capture": 0,
"items": [{
"type": "core",
"id": "core_desolate",
"amount": 5
}]
}]
}

View File

@@ -1,94 +1,94 @@
{
"map_id": "test",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 200,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 0,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": true,
"require_id": "",
"require_type": "",
"require_value": 0,
"coordinate": "0,0",
"step_count": 12,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 20,
"items": [{
"type": "fragment",
"amount": 1000
}]
}, {
"position": 2,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 6,
"capture": 10
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10000,
"step_type": ["plusstamina","speedlimit"],
"plus_stamina_value": 2,
"speed_limit_value": 5,
"restrict_id": "fractureray",
"restrict_type": "song_id"
}, {
"position": 11,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 500
}, {
"type": "fragment",
"amount": 125
}]
}]
{
"map_id": "test",
"is_legacy": false,
"is_beyond": false,
"beyond_health": 200,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 0,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": true,
"require_id": "",
"require_type": "",
"require_value": 0,
"coordinate": "0,0",
"step_count": 12,
"custom_bg": "",
"stamina_cost": 2,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 20,
"items": [{
"type": "fragment",
"amount": 1000
}]
}, {
"position": 2,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id"
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 20
}, {
"position": 6,
"capture": 10
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2
}, {
"position": 8,
"capture": 10
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10000,
"step_type": ["plusstamina","speedlimit"],
"plus_stamina_value": 2,
"speed_limit_value": 5,
"restrict_id": "fractureray",
"restrict_type": "song_id"
}, {
"position": 11,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 500
}, {
"type": "fragment",
"amount": 125
}]
}]
}

View File

@@ -1,120 +1,120 @@
{
"map_id": "test2",
"is_legacy": true,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 0,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": true,
"require_id": "",
"require_type": "",
"require_value": 0,
"coordinate": "200,200",
"step_count": 12,
"custom_bg": "",
"stamina_cost": 1,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 1000
}]
}, {
"position": 2,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"],
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}, {
"type": "fragment",
"amount": 125
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 60,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 6,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 8,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 11,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 50
}, {
"type": "fragment",
"amount": 125
}]
}]
{
"map_id": "test2",
"is_legacy": true,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 0,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": true,
"require_id": "",
"require_type": "",
"require_value": 0,
"coordinate": "200,200",
"step_count": 12,
"custom_bg": "",
"stamina_cost": 1,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 1000
}]
}, {
"position": 2,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"],
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}, {
"type": "fragment",
"amount": 125
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 60,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 6,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 8,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 10,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 11,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 50
}, {
"type": "fragment",
"amount": 125
}]
}]
}

View File

@@ -0,0 +1,114 @@
{
"map_id": "test3",
"is_legacy": true,
"is_beyond": false,
"beyond_health": 100,
"character_affinity": [],
"affinity_multiplier": [],
"chapter": 0,
"available_from": -1,
"available_to": 2106124800000,
"is_repeatable": false,
"require_id": "",
"require_type": "",
"require_value": 0,
"coordinate": "300,200",
"step_count": 12,
"custom_bg": "",
"stamina_cost": 1,
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"position": 0,
"capture": 10
}, {
"position": 1,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 1000
}]
}, {
"position": 2,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"items": [{
"type": "memory",
"amount": 150
}]
}, {
"position": 3,
"capture": 10,
"restrict_id": "base",
"restrict_type": "pack_id",
"step_type": ["randomsong"],
"items": [{
"id": "supernova",
"type": "world_song"
}]
}, {
"position": 4,
"capture": 10,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"position": 5,
"capture": 10,
"step_type": ["speedlimit"],
"speed_limit_value": 60,
"items": [{
"id": "scenery_chap4",
"type": "world_unlock"
}]
}, {
"position": 6,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 1250
}]
}, {
"position": 7,
"capture": 10,
"step_type": ["plusstamina"],
"plus_stamina_value": 2,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 8,
"capture": 10,
"items": [{
"type": "fragment",
"amount": 125
}]
}, {
"position": 9,
"capture": 10,
"items": [{
"type": "memory",
"amount": 150
}]
}, {
"position": 10,
"capture": 10,
"items": [{"id": "2", "type": "character"}]
}, {
"position": 11,
"capture": 0,
"items": [{
"type": "core",
"id": "core_crimson",
"amount": 50
}, {
"type": "fragment",
"amount": 125
}]
}]
}

View File

@@ -0,0 +1,556 @@
[
{
"name": "core",
"items": [
{
"type": "pack",
"id": "core",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "shiawase",
"items": [
{
"type": "pack",
"id": "shiawase",
"is_available": true
},
{
"type": "character",
"id": "kou",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "dynamix",
"items": [
{
"type": "pack",
"id": "dynamix",
"is_available": true
},
{
"type": "character",
"id": "sapphire",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "mirai",
"items": [
{
"type": "pack",
"id": "mirai",
"is_available": true
},
{
"type": "character",
"id": "lethe",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "yugamu",
"items": [
{
"type": "pack",
"id": "yugamu",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "lanota",
"items": [
{
"type": "pack",
"id": "lanota",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "nijuusei",
"items": [
{
"type": "pack",
"id": "nijuusei",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "rei",
"items": [
{
"type": "pack",
"id": "rei",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "tonesphere",
"items": [
{
"type": "pack",
"id": "tonesphere",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "groovecoaster",
"items": [
{
"type": "pack",
"id": "groovecoaster",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "zettai",
"items": [
{
"type": "pack",
"id": "zettai",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "chunithm",
"items": [
{
"type": "pack",
"id": "chunithm",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"price": 300,
"orig_price": 300
},
{
"name": "prelude",
"items": [
{
"type": "pack",
"id": "prelude",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"price": 400,
"orig_price": 400,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "omatsuri",
"items": [
{
"type": "pack",
"id": "omatsuri",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "vs",
"items": [
{
"type": "pack",
"id": "vs",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "extend",
"items": [
{
"type": "pack",
"id": "extend",
"is_available": true
},
{
"type": "core",
"amount": 7,
"id": "core_generic",
"is_available": true
}
],
"price": 0,
"orig_price": 700,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "alice",
"items": [
{
"type": "pack",
"id": "alice",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "alice_append_1",
"items": [
{
"type": "pack",
"id": "alice_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "ongeki",
"items": [
{
"type": "pack",
"id": "ongeki",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "maimai",
"items": [
{
"type": "pack",
"id": "maimai",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "chunithm_append_1",
"items": [
{
"type": "pack",
"id": "chunithm_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "observer_append_1",
"items": [
{
"type": "pack",
"id": "observer_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "observer",
"items": [
{
"type": "pack",
"id": "observer",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "observer_append_2",
"items": [
{
"type": "pack",
"id": "observer_append_2",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "wacca",
"items": [
{
"type": "pack",
"id": "wacca",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "nijuusei_append_1",
"items": [
{
"type": "pack",
"id": "nijuusei_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "dividedheart",
"items": [
{
"type": "pack",
"id": "dividedheart",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "musedash",
"items": [
{
"type": "pack",
"id": "musedash",
"is_available": true
},
{
"type": "character",
"id": "marija",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 400,
"orig_price": 400
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
AudioOffset:408
-
timing(0,210.00,4.00);
(1142,4);
flick(1142,0.50,0.50,0.00,1.00);
(1571,2);
(2857,1);
(3142,2);
(3428,3);
(3857,4);
(4142,3);
(4571,2);
(5000,1);
(5428,3);
arc(5714,6857,0.00,0.50,s,1.00,1.00,0,none,false);
arc(6857,8000,0.50,0.00,s,1.00,1.00,0,none,false);
arc(8000,9142,0.00,0.50,s,1.00,1.00,0,none,false);
arc(9142,10285,0.50,0.00,s,1.00,1.00,0,none,false);
arc(10285,11428,1.00,0.50,s,1.00,1.00,1,none,false);
arc(11428,12571,0.50,1.00,s,1.00,1.00,1,none,false);
arc(12571,13714,1.00,0.50,s,1.00,1.00,1,none,false);
arc(13714,14571,0.50,1.00,s,1.00,1.00,1,none,false);

View File

812
latest version/main.py Normal file
View File

@@ -0,0 +1,812 @@
# encoding: utf-8
from flask import Flask, json, request, jsonify, make_response, send_from_directory
from logging.config import dictConfig
from setting import Config
import base64
import server.auth
import server.info
import server.setme
import server.arcscore
import web.login
import web.index
import api.api_main
import server.arcworld
import server.arcdownload
import server.arcpurchase
import server.init
import server.character
import server.arclinkplay
from udpserver.udp_main import link_play
import os
import sys
from multiprocessing import Process, Pipe
app = Flask(__name__)
wsgi_app = app.wsgi_app
os.chdir(sys.path[0]) # 更改工作路径,以便于愉快使用相对路径
app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.bp)
conn1, conn2 = Pipe()
def add_url_prefix(url, strange_flag=False):
# 给url加前缀返回字符串
if not url or not Config.GAME_API_PREFIX:
return Config.GAME_API_PREFIX + url
prefix = Config.GAME_API_PREFIX
if prefix[0] != '/':
prefix = '/' + prefix
if prefix[-1] == '/':
prefix = prefix[:-1]
if url[0] != '/':
r = '/' + url
else:
r = url
if strange_flag and prefix.count('/') >= 1: # 为了方便处理双斜杠
t = prefix[::-1]
t = t[t.find('/')+1:]
prefix = t[::-1]
return prefix + r
def error_return(error_code, extra={}): # 错误返回
# -7 处理交易时发生了错误
# -5 所有的曲目都已经下载完毕
# -4 您的账号已在别处登录
# -3 无法连接至服务器
# 2 Arcaea服务器正在维护
# 9 新版本请等待几分钟
# 100 无法在此ip地址下登录游戏
# 101 用户名占用
# 102 电子邮箱已注册
# 103 已有一个账号由此设备创建
# 104 用户名密码错误
# 105 24小时内登入两台设备
# 106 121 账户冻结
# 107 你没有足够的体力
# 113 活动已结束
# 114 该活动已结束,您的成绩不会提交
# 115 请输入有效的电子邮箱地址
# 120 封号警告
# 122 账户暂时冻结
# 123 账户被限制
# 124 你今天不能再使用这个IP地址创建新的账号
# 150 非常抱歉您已被限制使用此功能
# 151 目前无法使用此功能
# 401 用户不存在
# 403 无法连接至服务器
# 501 502 -6 此物品目前无法获取
# 504 无效的序列码
# 505 此序列码已被使用
# 506 你已拥有了此物品
# 601 好友列表已满
# 602 此用户已是好友
# 604 你不能加自己为好友
# 903 下载量超过了限制请24小时后重试
# 905 请在再次使用此功能前等待24小时
# 1001 设备数量达到上限
# 1002 此设备已使用过此功能
# 1201 房间已满
# 1202 房间号码无效
# 1203 请将Arcaea更新至最新版本
# 1205 此房间目前无法加入
# 9801 下载歌曲时发生问题,请再试一次
# 9802 保存歌曲时发生问题,请检查设备空间容量
# 9803 下载已取消
# 9905 没有在云端发现任何数据
# 9907 更新数据时发生了问题
# 9908 服务器只支持最新的版本请更新Arcaea
# 其它 发生未知错误
if extra:
return jsonify({
"success": False,
"error_code": error_code,
"extra": extra
})
else:
return jsonify({
"success": False,
"error_code": error_code
})
@app.route('/')
def hello():
return "Hello World!"
# 自定义路径
@app.route('/favicon.ico', methods=['GET']) # 图标
def favicon():
# Pixiv ID: 82374369
# 我觉得这张图虽然并不是那么精细,但很有感觉,色彩的强烈对比下给人带来一种惊艳
# 然后在压缩之下什么也看不清了:(
return app.send_static_file('favicon.ico')
@app.route(add_url_prefix('/auth/login'), methods=['POST']) # 登录接口
def login():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(1203)
headers = request.headers
id_pwd = headers['Authorization']
id_pwd = base64.b64decode(id_pwd[6:]).decode()
name, password = id_pwd.split(':', 1)
if 'DeviceId' in headers:
device_id = headers['DeviceId']
else:
device_id = 'low_version'
token, error_code, extra = server.auth.arc_login(
name, password, device_id, request.remote_addr)
if not error_code:
r = {"success": True, "token_type": "Bearer"}
r['access_token'] = token
return jsonify(r)
else:
if extra:
return error_return(error_code, extra)
else:
return error_return(error_code)
@app.route(add_url_prefix('/user'), methods=['POST']) # 注册接口
def register():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(5)
name = request.form['name']
password = request.form['password']
email = request.form['email']
if 'device_id' in request.form:
device_id = request.form['device_id']
else:
device_id = 'low_version'
user_id, token, error_code = server.auth.arc_register(
name, password, device_id, email, request.remote_addr)
if user_id is not None:
r = {"success": True, "value": {
'user_id': user_id, 'access_token': token}}
return jsonify(r)
else:
return error_return(error_code)
# 集成式请求,没想到什么好办法处理,就先这样写着
@app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
@server.auth.auth_required(request)
def aggregate(user_id):
calls = request.args.get('calls')
if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
r = server.info.arc_aggregate_small(user_id)
else:
r = server.info.arc_aggregate_big(user_id)
return jsonify(r)
@app.route(add_url_prefix('/user/me'), methods=['GET']) # 用户信息给baa查分器用的
@server.auth.auth_required(request)
def user_me(user_id):
r = server.info.arc_aggregate_small(user_id)
if r['success']:
r['value'] = r['value'][0]['value']
return jsonify(r)
@app.route(add_url_prefix('/user/me/character'), methods=['POST']) # 角色切换
@server.auth.auth_required(request)
def character_change(user_id):
character_id = request.form['character']
skill_sealed = request.form['skill_sealed']
flag = server.setme.change_char(user_id, character_id, skill_sealed)
if flag:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": character_id
}
})
else:
return error_return(108)
# 角色觉醒切换
@app.route(add_url_prefix('/<path:path>/toggle_uncap', True), methods=['POST'])
@server.auth.auth_required(request)
def character_uncap(user_id, path):
character_id = int(path[path.find('character')+10:])
r = server.setme.change_char_uncap(user_id, character_id)
if r is not None:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": [r]
}
})
else:
return error_return(108)
# 角色觉醒
@app.route(add_url_prefix('/<path:path>/uncap', True), methods=['POST'])
@server.auth.auth_required(request)
def character_first_uncap(user_id, path):
character_id = int(path[path.find('character')+10:])
r = server.character.char_uncap(user_id, character_id)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
# 角色使用以太之滴
@app.route(add_url_prefix('/<path:path>/exp', True), methods=['POST'])
@server.auth.auth_required(request)
def character_exp(user_id, path):
character_id = int(path[path.find('character')+10:])
amount = int(request.form['amount'])
r = server.character.char_use_core(user_id, character_id, amount)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/friend/me/add'), methods=['POST']) # 加好友
@server.auth.auth_required(request)
def add_friend(user_id):
friend_code = request.form['friend_code']
friend_id = server.auth.code_get_id(friend_code)
if friend_id is not None:
r = server.setme.arc_add_friend(user_id, friend_id)
if r is not None and r != 602 and r != 604:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"updatedAt": "2020-09-07T07:32:12.740Z",
"createdAt": "2020-09-06T10:05:18.471Z",
"friends": r
}
})
else:
if r is not None:
return error_return(r)
else:
return error_return(108)
else:
return error_return(401)
@app.route(add_url_prefix('/friend/me/delete'), methods=['POST']) # 删好友
@server.auth.auth_required(request)
def delete_friend(user_id):
friend_id = int(request.form['friend_id'])
if friend_id is not None:
r = server.setme.arc_delete_friend(user_id, friend_id)
if r is not None:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"updatedAt": "2020-09-07T07:32:12.740Z",
"createdAt": "2020-09-06T10:05:18.471Z",
"friends": r
}
})
else:
return error_return(108)
else:
return error_return(401)
# 好友排名默认最多50
@app.route(add_url_prefix('/score/song/friend'), methods=['GET'])
@server.auth.auth_required(request)
def song_score_friend(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_friend(user_id, song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song/me'), methods=['GET']) # 我的排名默认最多20
@server.auth.auth_required(request)
def song_score_me(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_me(user_id, song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song'), methods=['GET']) # TOP20
@server.auth.auth_required(request)
def song_score_top(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_top(song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song'), methods=['POST']) # 成绩上传
@server.auth.auth_required(request)
def song_score_post(user_id):
song_token = request.form['song_token']
song_hash = request.form['song_hash']
song_id = request.form['song_id']
difficulty = int(request.form['difficulty'])
score = int(request.form['score'])
shiny_perfect_count = int(request.form['shiny_perfect_count'])
perfect_count = int(request.form['perfect_count'])
near_count = int(request.form['near_count'])
miss_count = int(request.form['miss_count'])
health = int(request.form['health'])
modifier = int(request.form['modifier'])
beyond_gauge = int(request.form['beyond_gauge'])
clear_type = int(request.form['clear_type'])
submission_hash = request.form['submission_hash']
# 增加成绩校验
if not server.arcscore.arc_score_check(user_id, song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type, song_token, song_hash, submission_hash):
return error_return(107)
r, re = server.arcscore.arc_score_post(user_id, song_id, difficulty, score, shiny_perfect_count,
perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type)
if re:
return jsonify({
"success": True,
"value": re
})
else:
return error_return(108)
# 成绩上传所需的token显然我不想验证
@app.route(add_url_prefix('/score/token'), methods=['GET'])
def score_token():
return jsonify({
"success": True,
"value": {
"token": "1145141919810"
}
})
# 世界模式成绩上传所需的token无验证
@app.route(add_url_prefix('/score/token/world'), methods=['GET'])
@server.auth.auth_required(request)
def score_token_world(user_id):
args = request.args
r = server.arcworld.play_world_song(user_id, args)
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/user/me/save'), methods=['GET']) # 从云端同步
@server.auth.auth_required(request)
def cloud_get(user_id):
r = server.arcscore.arc_all_get(user_id)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/user/me/save'), methods=['POST']) # 向云端同步
@server.auth.auth_required(request)
def cloud_post(user_id):
scores_data = request.form['scores_data']
clearlamps_data = request.form['clearlamps_data']
clearedsongs_data = request.form['clearedsongs_data']
unlocklist_data = request.form['unlocklist_data']
installid_data = request.form['installid_data']
devicemodelname_data = request.form['devicemodelname_data']
story_data = request.form['story_data']
server.arcscore.arc_all_post(user_id, scores_data, clearlamps_data, clearedsongs_data,
unlocklist_data, installid_data, devicemodelname_data, story_data)
return jsonify({
"success": True,
"value": {
"user_id": user_id
}
})
@app.route(add_url_prefix('/purchase/me/redeem'), methods=['POST']) # 兑换码
@server.auth.auth_required(request)
def redeem(user_id):
code = request.form['code']
fragment, error_code = server.arcpurchase.claim_user_redeem(
user_id, code)
if not error_code:
if fragment > 0:
return jsonify({
"success": True,
"value": {"coupon": "fragment"+str(fragment)}
})
else:
return jsonify({
"success": True,
"value": {"coupon": ""}
})
else:
return error_return(error_code)
# 礼物确认
@app.route(add_url_prefix('/present/me/claim/<present_id>'), methods=['POST'])
@server.auth.auth_required(request)
def claim_present(user_id, present_id):
flag = server.arcpurchase.claim_user_present(user_id, present_id)
if flag:
return jsonify({
"success": True
})
else:
return error_return(108)
# 购买体力
@app.route(add_url_prefix('/purchase/me/stamina/<buy_stamina_type>'), methods=['POST'])
@server.auth.auth_required(request)
def purchase_stamina(user_id, buy_stamina_type):
if buy_stamina_type == 'fragment':
r, error_code = server.arcworld.buy_stamina_by_fragment(user_id)
else:
return error_return(108)
if error_code:
return error_return(error_code)
else:
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
# 购买world模式boost
@app.route(add_url_prefix('/purchase/me/item'), methods=['POST'])
@server.auth.auth_required(request)
def prog_boost(user_id):
re = {"success": False}
if 'item_id' in request.form:
if request.form['item_id'] == 'prog_boost_300':
ticket, error_code = server.arcpurchase.get_prog_boost(user_id)
if error_code:
return error_return(error_code)
re = {
"success": True,
"value": {'ticket': ticket}
}
elif request.form['item_id'] == 'stamina6':
r, error_code = server.arcworld.buy_stamina_by_ticket(user_id)
if error_code:
return error_return(error_code)
re = {
"success": True,
"value": r
}
return jsonify(re)
@app.route(add_url_prefix('/purchase/me/pack'), methods=['POST']) # 曲包和单曲购买
@server.auth.auth_required(request)
def pack(user_id):
if 'pack_id' in request.form:
return jsonify(server.arcpurchase.buy_thing(user_id, request.form['pack_id']))
if 'single_id' in request.form:
return jsonify(server.arcpurchase.buy_thing(user_id, request.form['single_id']))
return jsonify({"success": True})
# 单曲购买信息获取
@app.route(add_url_prefix('/purchase/bundle/single'), methods=['GET'])
def single():
return jsonify({
"success": True,
"value": server.arcpurchase.get_single_purchase()
})
@app.route(add_url_prefix('/world/map/me'), methods=['GET']) # 获得世界模式信息,所有地图
@server.auth.auth_required(request)
def world_all(user_id):
return jsonify({
"success": True,
"value": {
"current_map": server.arcworld.get_current_map(user_id),
"user_id": user_id,
"maps": server.arcworld.get_world_all(user_id)
}
})
@app.route(add_url_prefix('/world/map/me'), methods=['POST']) # 进入地图
@server.auth.auth_required(request)
def world_in(user_id):
map_id = request.form['map_id']
flag = server.arcworld.unlock_user_world(user_id, map_id)
return jsonify({
"success": flag,
"value": server.arcworld.get_user_world(user_id, map_id)
})
# 获得单个地图完整信息
@app.route(add_url_prefix('/world/map/me/<map_id>'), methods=['GET'])
@server.auth.auth_required(request)
def world_one(user_id, map_id):
server.arcworld.change_user_current_map(user_id, map_id)
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"current_map": map_id,
"maps": [server.arcworld.get_user_world_info(user_id, map_id)]
}
})
@app.route(add_url_prefix('/serve/download/me/song'), methods=['GET']) # 歌曲下载
@server.auth.auth_required(request)
def download_song(user_id):
song_ids = request.args.getlist('sid')
if server.arcdownload.is_able_download(user_id):
re = {}
if not song_ids:
re = server.arcdownload.get_all_songs(user_id)
else:
re = server.arcdownload.get_some_songs(user_id, song_ids)
return jsonify({
"success": True,
"value": re
})
else:
return error_return(903)
@app.route('/download/<path:file_path>', methods=['GET']) # 下载
def download(file_path):
try:
t = request.args.get('t')
message = server.arcdownload.is_token_able_download(t)
if message == 0:
path = os.path.join('./database/songs', file_path)
if os.path.isfile(path) and not('../' in path or '..\\' in path):
return send_from_directory('./database/songs', file_path, as_attachment=True)
else:
return error_return(109)
else:
return error_return(message)
except:
return error_return(108)
# 创建房间
@app.route(add_url_prefix('/multiplayer/me/room/create'), methods=['POST'])
@server.auth.auth_required(request)
def room_create(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.create_room(
conn1, user_id, client_song_map)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
# 加入房间
@app.route(add_url_prefix('/multiplayer/me/room/join/<room_code>'), methods=['POST'])
@server.auth.auth_required(request)
def room_join(user_id, room_code):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.join_room(
conn1, user_id, client_song_map, room_code)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
@app.route(add_url_prefix('/multiplayer/me/update'), methods=['POST']) # 更新房间
@server.auth.auth_required(request)
def multiplayer_update(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
token = request.json['token']
error_code, value = server.arclinkplay.update_room(conn1, user_id, token)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
@app.route(add_url_prefix('/user/me/request_delete'), methods=['POST']) # 删除账号
@server.auth.auth_required(request)
def user_delete(user_id):
return error_return(151), 404
# 三个设置,写在最后降低优先级
@app.route(add_url_prefix('/<path:path>', True), methods=['POST'])
@server.auth.auth_required(request)
def sys_set(user_id, path):
set_arg = path[5:]
value = request.form['value']
server.setme.arc_sys_set(user_id, value, set_arg)
r = server.info.arc_aggregate_small(user_id)
r['value'] = r['value'][0]['value']
return jsonify(r)
def main():
log_dict = {
'version': 1,
'root': {
'level': 'INFO',
'handlers': ['wsgi', 'error_file']
},
'handlers': {
'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "ERROR",
"formatter": "default",
"filename": "./log/error.log"
}
},
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
}
}
if Config.ALLOW_LOG_INFO:
log_dict['root']['handlers'] = ['wsgi', 'info_file', 'error_file']
log_dict['handlers']['info_file'] = {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "INFO",
"formatter": "default",
"filename": "./log/info.log"
}
dictConfig(log_dict)
if not server.init.check_before_run(app):
app.logger.error('Something wrong. The server will not run.')
input('Press ENTER key to exit.')
sys.exit()
app.logger.info("Start to initialize data in 'songfile' table...")
try:
error = server.arcdownload.initialize_songfile()
except:
error = 'Something wrong.'
if error:
app.logger.warning(error)
else:
app.logger.info('Complete!')
if Config.UDP_PORT and Config.UDP_PORT != '':
process = [Process(target=link_play, args=(
conn2, Config.HOST, int(Config.UDP_PORT)))]
[p.start() for p in process]
app.logger.info("UDP server is running...")
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
[p.join() for p in process]
else:
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
if __name__ == '__main__':
main()
# Made By Lost 2020.9.11

View File

@@ -0,0 +1,2 @@
flask
cryptography

2
latest version/run.bat Normal file
View File

@@ -0,0 +1,2 @@
cd /d %~dp0
python -B main.py

View File

View File

@@ -0,0 +1,200 @@
import os
import hashlib
from flask import url_for
import sqlite3
from server.sql import Connect
import time
from setting import Config
time_limit = Config.DOWNLOAD_TIMES_LIMIT # 每个玩家24小时下载次数限制
time_gap_limit = Config.DOWNLOAD_TIME_GAP_LIMIT # 下载链接有效秒数
def get_file_md5(file_path):
# 计算文件MD5
if not os.path.isfile(file_path):
return None
myhash = hashlib.md5()
with open(file_path, 'rb') as f:
while True:
b = f.read(8096)
if not b:
break
myhash.update(b)
return myhash.hexdigest()
def get_url(file_path, **kwargs):
# 获取下载地址
t = ''
if 't' in kwargs:
t = kwargs['t']
if Config.DOWNLOAD_LINK_PREFIX:
prefix = Config.DOWNLOAD_LINK_PREFIX
if prefix[-1] != '/':
prefix += '/'
return prefix + file_path + '?t=' + t
else:
return url_for('download', file_path=file_path, t=t, _external=True)
def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True):
# 获取一首歌的下载链接,返回字典
dir_list = os.listdir(os.path.join(file_dir, song_id))
re = {}
now = int(time.time())
c.execute('''delete from download_token where user_id=:a and song_id=:b''', {
'a': user_id, 'b': song_id})
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg']:
token = hashlib.md5(
(str(user_id) + song_id + i + str(now)).encode(encoding='UTF-8')).hexdigest()
if i == 'base.ogg':
c.execute(
'''select md5 from songfile where song_id=:a and file_type=-1''', {'a': song_id})
x = c.fetchone()
if x:
checksum = x[0]
else:
checksum = get_file_md5(os.path.join(
file_dir, song_id, 'base.ogg'))
if url_flag:
re['audio'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/base.ogg', t=token)}
else:
re['audio'] = {"checksum": checksum}
else:
if 'chart' not in re:
re['chart'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=:b''', {'a': song_id, 'b': int(i[0])})
x = c.fetchone()
if x:
checksum = x[0]
else:
checksum = get_file_md5(os.path.join(file_dir, song_id, i))
if url_flag:
re['chart'][i[0]] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/'+i, t=token)}
else:
re['chart'][i[0]] = {"checksum": checksum}
if url_flag:
c.execute('''insert into download_token values(:a,:b,:c,:d,:e)''', {
'a': user_id, 'b': song_id, 'c': i, 'd': token, 'e': now})
return {song_id: re}
def get_all_songs(user_id, file_dir='./database/songs', url_flag=True):
# 获取所有歌的下载链接,返回字典
dir_list = os.listdir(file_dir)
re = {}
with Connect() as c:
for i in dir_list:
if os.path.isdir(os.path.join(file_dir, i)):
re.update(get_one_song(c, user_id, i, file_dir, url_flag))
return re
def get_some_songs(user_id, song_ids):
# 获取一些歌的下载链接,返回字典
re = {}
with Connect() as c:
for song_id in song_ids:
re.update(get_one_song(c, user_id, song_id))
return re
def is_token_able_download(t):
# token是否可以下载返回错误码0即可以
errorcode = 108
with Connect() as c:
c.execute('''select * from download_token where token = :t limit 1''',
{'t': t})
x = c.fetchone()
now = int(time.time())
if x and now - x[4] <= time_gap_limit:
c.execute(
'''select count(*) from user_download where user_id = :a''', {'a': x[0]})
y = c.fetchone()
if y and y[0] <= time_limit:
c.execute('''insert into user_download values(:a,:b,:c)''', {
'a': x[0], 'b': x[3], 'c': now})
errorcode = 0
else:
errorcode = 903
else:
errorcode = 108
return errorcode
def is_able_download(user_id):
# 是否可以下载,返回布尔值
f = False
with Connect() as c:
now = int(time.time())
c.execute(
'''delete from user_download where user_id = :a and time <= :b''', {'a': user_id, 'b': now - 24*3600})
c.execute(
'''select count(*) from user_download where user_id = :a''', {'a': user_id})
y = c.fetchone()
if y and y[0] <= time_limit:
f = True
else:
f = False
return f
def initialize_one_songfile(c, song_id, file_dir='./database/songs'):
# 计算并添加歌曲md5到表中无返回
dir_list = os.listdir(os.path.join(file_dir, song_id))
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg']:
if i == 'base.ogg':
c.execute('''insert into songfile values(:a,-1,:c)''', {
'a': song_id, 'c': get_file_md5(os.path.join(file_dir, song_id, 'base.ogg'))})
else:
c.execute('''insert into songfile values(:a,:b,:c)''', {
'a': song_id, 'b': int(i[0]), 'c': get_file_md5(os.path.join(file_dir, song_id, i))})
return
def initialize_songfile(file_dir='./database/songs'):
# 初始化歌曲数据的md5表返回错误信息
error = None
try:
dir_list = os.listdir(file_dir)
except:
error = 'OS error!'
return error
try:
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
except:
error = 'Database error!'
return error
try:
c.execute('''delete from songfile''')
for i in dir_list:
if os.path.isdir(os.path.join(file_dir, i)):
initialize_one_songfile(c, i, file_dir)
except:
error = 'Initialization error!'
finally:
conn.commit()
conn.close()
return error

View File

@@ -0,0 +1,113 @@
from .sql import Connect
import base64
def get_song_unlock(client_song_map):
# 处理可用歌曲bit返回bytes
user_song_unlock = [0] * 512
for i in range(0, 1024, 2):
x = 0
y = 0
if str(i) in client_song_map:
if client_song_map[str(i)][0]:
x += 1
if client_song_map[str(i)][1]:
x += 2
if client_song_map[str(i)][2]:
x += 4
if client_song_map[str(i)][3]:
x += 8
if str(i+1) in client_song_map:
if client_song_map[str(i+1)][0]:
y += 1
if client_song_map[str(i+1)][1]:
y += 2
if client_song_map[str(i+1)][2]:
y += 4
if client_song_map[str(i+1)][3]:
y += 8
user_song_unlock[i // 2] = y*16 + x
return bytes(user_song_unlock)
def create_room(conn, user_id, client_song_map):
# 创建房间,返回错误码和房间与用户信息
error_code = 108
with Connect() as c:
c.execute('''select name from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
name = x[0]
song_unlock = get_song_unlock(client_song_map)
conn.send((1, name, song_unlock))
data = conn.recv()
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': str(data[3]),
'key': (base64.b64encode(data[4])).decode(),
'playerId': str(data[5]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(song_unlock)).decode()
}
return error_code, None
def join_room(conn, user_id, client_song_map, room_code):
# 加入房间,返回错误码和房间与用户信息
error_code = 108
with Connect() as c:
c.execute('''select name from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
name = x[0]
song_unlock = get_song_unlock(client_song_map)
conn.send((2, name, song_unlock, room_code))
data = conn.recv()
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': str(data[3]),
'key': (base64.b64encode(data[4])).decode(),
'playerId': str(data[5]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(data[6])).decode()
}
else:
error_code = data[0]
return error_code, None
def update_room(conn, user_id, token):
# 更新房间,返回错误码和房间与用户信息
error_code = 108
conn.send((3, int(token)))
data = conn.recv()
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': token,
'key': (base64.b64encode(data[3])).decode(),
'playerId': str(data[4]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(data[5])).decode()
}
else:
error_code = data[0]
return error_code, None

View File

@@ -0,0 +1,282 @@
from server.sql import Connect
import server.item
import server.character
import time
import json
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def get_purchase(c, type='pack'):
# 读取packs内容返回字典列表
c.execute(
'''select * from purchase where purchase_name in (select purchase_name from purchase_item where type = :a)''', {'a': type})
x = c.fetchall()
if not x:
return []
re = []
for i in x:
items = []
c.execute(
'''select a.*, b.amount from item a, purchase_item b where a.item_id=b.item_id and a.type=b.type and b.purchase_name=:name''', {'name': i[0]})
y = c.fetchall()
t = None
if y:
for j in y:
if j[3]:
amount = j[3]
else:
amount = 1
if i[0] == j[0]:
# 物品排序,否则客户端报错
t = {
"type": j[1],
"id": j[0],
"is_available": int2b(j[2]),
'amount': amount
}
else:
items.append({
"type": j[1],
"id": j[0],
"is_available": int2b(j[2]),
"amount": amount
})
if t is not None:
# 放到列表头
items = [t, items]
r = {"name": i[0],
"items": items,
"price": i[1],
"orig_price": i[2]}
if i[3] > 0:
r['discount_from'] = i[3]
if i[4] > 0:
r['discount_to'] = i[4]
re.append(r)
return re
def get_single_purchase():
# main里面没开数据库这里写一下代替
re = []
with Connect() as c:
re = get_purchase(c, type='single')
return re
def buy_item(c, user_id, price):
# 购买接口,返回成功与否标识符和剩余源点数
c.execute('''select ticket from user where user_id = :a''',
{'a': user_id})
ticket = c.fetchone()
if ticket:
ticket = ticket[0]
else:
ticket = 0
if ticket < price:
return False, ticket
c.execute('''update user set ticket = :b where user_id = :a''',
{'a': user_id, 'b': ticket-price})
return True, ticket - price
def buy_thing(user_id, purchase_id):
# 购买物品接口,返回字典
success_flag = False
ticket = 0
packs = []
singles = []
characters = []
with Connect() as c:
c.execute('''select price, orig_price, discount_from, discount_to from purchase where purchase_name=:a''',
{'a': purchase_id})
x = c.fetchone()
price = 0
flag = False
if x:
price = x[0]
orig_price = x[1]
discount_from = x[2]
discount_to = x[3]
else:
return {
"success": False,
"error_code": 501
}
c.execute(
'''select item_id, type, amount from purchase_item where purchase_name=:a''', {'a': purchase_id})
x = c.fetchall()
if x:
now = int(time.time() * 1000)
if not(discount_from <= now <= discount_to):
price = orig_price
flag, ticket = buy_item(c, user_id, price)
if flag:
for i in x:
if i[2]:
amount = i[2]
else:
amount = 1
server.item.claim_user_item(c, user_id, i[0], i[1], amount)
success_flag = True
else:
return {
"success": False,
"error_code": 501
}
packs = server.item.get_user_items(c, user_id, 'pack')
singles = server.item.get_user_items(c, user_id, 'single')
characters = server.character.get_user_characters(c, user_id)
return {
"success": success_flag,
"value": {'user_id': user_id,
'ticket': ticket,
'packs': packs,
'singles': singles,
'characters': characters
}
}
def get_prog_boost(user_id):
# 世界模式源韵强化扣50源点返回剩余源点数
ticket = -1
with Connect() as c:
flag, ticket = buy_item(c, user_id, 50)
if flag:
c.execute('''update user set prog_boost = 1 where user_id = :a''', {
'a': user_id})
if ticket >= 0:
return ticket, None
else:
return 0, 108
def get_user_present(c, user_id):
# 获取用户奖励,返回字典列表
c.execute(
'''select * from present where present_id in (select present_id from user_present where user_id=:a)''', {'a': user_id})
x = c.fetchall()
re = []
now = int(time.time() * 1000)
if x:
for i in x:
if now <= int(i[1]):
c.execute(
'''select * from present_item where present_id=?''', (i[0],))
y = c.fetchall()
items = []
if y:
for j in y:
if j is not None:
items.append({
"type": j[2],
"id": j[1],
"amount": j[3]
})
re.append({'expire_ts': i[1],
'description': i[2],
'present_id': i[0],
'items': items
})
return re
def claim_user_present(user_id, present_id):
# 确认并删除用户奖励,返回成功与否的布尔值
flag = False
with Connect() as c:
c.execute('''select exists(select * from user_present where user_id=:a and present_id=:b)''',
{'a': user_id, 'b': present_id})
if c.fetchone() == (1,):
c.execute('''delete from user_present where user_id=:a and present_id=:b''',
{'a': user_id, 'b': present_id})
c.execute('''select * from present where present_id=:b''',
{'b': present_id})
x = c.fetchone()
now = int(time.time() * 1000)
if now <= int(x[1]):
# 处理memory
c.execute(
'''select * from present_item where present_id=?''', (x[0],))
y = c.fetchall()
flag = True
if y:
for j in y:
if j is not None:
flag = flag and server.item.claim_user_item(
c, user_id, j[1], j[2], j[3])
else:
# 过期
flag = False
return flag
def claim_user_redeem(user_id, code):
# 处理兑换码,返回碎片数量和错误码
fragment = 0
error_code = 108
with Connect() as c:
c.execute('''select * from redeem where code=:a''', {'a': code})
x = c.fetchone()
if not x:
return 0, 504
if x[1] == 0: # 一次性
c.execute(
'''select exists(select * from user_redeem where code=:a)''', {'a': code})
if c.fetchone() == (1,):
return 0, 505
elif x[1] == 1: # 每个玩家一次
c.execute('''select exists(select * from user_redeem where code=:a and user_id=:b)''',
{'a': code, 'b': user_id})
if c.fetchone() == (1,):
return 0, 506
c.execute('''insert into user_redeem values(:b,:a)''',
{'a': code, 'b': user_id})
c.execute('''select * from redeem_item where code=?''', (code,))
x = c.fetchall()
flag = True
if x:
for i in x:
if i[2] == 'fragment':
fragment += i[3]
else:
flag = flag and server.item.claim_user_item(
c, user_id, i[1], i[2], i[3])
if flag:
error_code = None
return fragment, error_code

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,664 @@
import json
from server.sql import Connect
from setting import Config
import server.item
import server.character
import server.info
import server.arcpurchase
import os
import time
import random
ETO_UNCAP_BONUS_PROGRESS = 7
LUNA_UNCAP_BONUS_PROGRESS = 7
AYU_UNCAP_BONUS_PROGRESS = 5
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def calc_stamina(max_stamina_ts, curr_stamina):
# 计算体力,返回剩余体力数值
stamina = int(server.info.MAX_STAMINA - (max_stamina_ts -
int(time.time()*1000)) / server.info.STAMINA_RECOVER_TICK)
if stamina >= server.info.MAX_STAMINA:
if curr_stamina >= server.info.MAX_STAMINA:
stamina = curr_stamina
else:
stamina = server.info.MAX_STAMINA
if stamina < 0:
stamina = 0
return stamina
def get_world_name(file_dir='./database/map'):
# 获取所有地图名称,返回列表
L = []
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.json':
L.append(os.path.splitext(file)[0])
return L
def get_world_info(map_id):
# 读取json文件内容返回字典
world_info = {}
with open('./database/map/'+map_id+'.json', 'r') as f:
world_info = json.load(f)
return world_info
def get_user_world_info(user_id, map_id):
# 读取json文件内容加上用户信息返回字典
info = get_world_info(map_id)
with Connect() as c:
c.execute('''select * from user_world where map_id = :a and user_id = :b''',
{'a': map_id, 'b': user_id})
x = c.fetchone()
if x:
info['curr_position'] = x[2]
info['curr_capture'] = x[3]
info['is_locked'] = int2b(x[4])
else:
info['curr_position'] = 0
info['curr_capture'] = 0
info['is_locked'] = True
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
return info
def get_current_map(user_id):
# 得到user的当前图返回字符串
re = ''
with Connect() as c:
c.execute('''select current_map from user where user_id = :a''',
{'a': user_id})
x = c.fetchone()
if x:
re = x[0]
return re
def get_world_all(user_id):
# 读取所有地图信息并处理,返回字典列表
re = []
worlds = get_world_name()
with Connect() as c:
for map_id in worlds:
info = get_world_info(map_id)
steps = info['steps']
del info['steps']
rewards = []
for step in steps:
if 'items' in step:
rewards.append(
{'items': step['items'], 'position': step['position']})
info['rewards'] = rewards
c.execute('''select * from user_world where map_id = :a and user_id = :b''',
{'a': map_id, 'b': user_id})
x = c.fetchone()
if x:
info['curr_position'] = x[2]
info['curr_capture'] = x[3]
info['is_locked'] = int2b(x[4])
else:
info['curr_position'] = 0
info['curr_capture'] = 0
info['is_locked'] = True
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
re.append(info)
return re
def get_available_maps():
# 获取当前可用图(用户设定的),返回字典列表
re = []
for i in Config.AVAILABLE_MAP:
info = get_world_info(i)
del info['steps']
try:
del info['is_locked']
del info['curr_position']
del info['curr_capture']
except:
pass
re.append(info)
return re
def get_user_world(user_id, map_id):
# 获取用户图信息,返回字典
re = {}
with Connect() as c:
c.execute('''select * from user_world where map_id = :a and user_id = :b''',
{'a': map_id, 'b': user_id})
x = c.fetchone()
re = {
"user_id": user_id,
"curr_position": 0,
"curr_capture": 0,
"is_locked": True,
"map_id": map_id
}
if x:
re['curr_position'] = x[2]
re['curr_capture'] = x[3]
re['is_locked'] = int2b(x[4])
else:
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
return re
def unlock_user_world(user_id, map_id):
# 解锁用户的图,返回成功与否布尔值
with Connect() as c:
c.execute(
'''select is_locked from user_world where map_id=? and user_id=?''', (map_id, user_id))
x = c.fetchone()
if x:
is_locked = x[0]
else:
is_locked = 1
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
if is_locked == 1:
map_info = get_world_info(map_id)
if 'require_type' in map_info and map_info['require_type'] != '':
if map_info['require_type'] in ['pack', 'single']:
c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
(user_id, map_info['require_id'], map_info['require_type']))
if c.fetchone() == (0,):
return False
c.execute(
'''update user_world set is_locked=0 where user_id=? and map_id=?''', (user_id, map_id))
return True
def change_user_current_map(user_id, map_id):
# 改变用户当前图
with Connect() as c:
c.execute('''update user set current_map = :a where user_id=:b''', {
'a': map_id, 'b': user_id})
return None
def play_world_song(user_id, args):
# 声明是世界模式的打歌,并且记录加成信息,返回字典
r = {}
with Connect() as c:
stamina_multiply = 1
fragment_multiply = 100
prog_boost_multiply = 0
if 'stamina_multiply' in args:
stamina_multiply = int(args['stamina_multiply'])
if 'fragment_multiply' in args:
fragment_multiply = int(args['fragment_multiply'])
if 'prog_boost_multiply' in args:
c.execute('''select prog_boost from user where user_id=:a''', {
'a': user_id})
x = c.fetchone()
if x and x[0] == 1:
prog_boost_multiply = 300
c.execute('''delete from world_songplay where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': args['song_id'], 'c': args['difficulty']})
c.execute('''insert into world_songplay values(:a,:b,:c,:d,:e,:f)''', {
'a': user_id, 'b': args['song_id'], 'c': args['difficulty'], 'd': stamina_multiply, 'e': fragment_multiply, 'f': prog_boost_multiply})
c.execute('''select current_map from user where user_id = :a''', {
'a': user_id})
map_id = c.fetchone()[0]
info = get_world_info(map_id)
# 体力计算
c.execute(
'''select max_stamina_ts, stamina from user where user_id=?''', (user_id,))
x = c.fetchone()
max_stamina_ts = x[0] if x and x[0] is not None else 0
stamina = x[1] if x and x[1] is not None else 12
now = int(time.time() * 1000)
# 体力不足
if calc_stamina(max_stamina_ts, stamina) < info['stamina_cost']:
return {}
stamina = calc_stamina(max_stamina_ts, stamina) - \
info['stamina_cost'] * stamina_multiply
max_stamina_ts = now + server.info.STAMINA_RECOVER_TICK * \
(server.info.MAX_STAMINA - stamina)
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
r = {
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"token": "13145201919810"
}
return r
def climb_step(user_id, map_id, step, prev_capture, prev_position):
# 爬梯子,返回奖励列表,台阶列表,当前的位置和坐标,图信息
info = get_world_info(map_id)
step_count = int(info['step_count'])
restrict_ids = [[]] * step_count
capture = [0] * step_count
reward_bundle = [""] * step_count # 暂且不用
restrict_id = [""] * step_count
restrict_type = [""] * step_count
items = [[]] * step_count
step_type = [[]] * step_count
speed_limit_value = [0] * step_count
plus_stamina_value = [0] * step_count
for i in info['steps']:
capture[i['position']] = i['capture']
if 'items' in i:
items[i['position']] = i['items']
if 'restrict_id' in i:
restrict_id[i['position']] = i['restrict_id']
if 'restrict_ids' in i:
restrict_ids[i['position']] = i['restrict_ids']
if 'restrict_type' in i:
restrict_type[i['position']] = i['restrict_type']
if 'step_type' in i:
step_type[i['position']] = i['step_type']
if "speedlimit" in i['step_type']:
speed_limit_value[i['position']] = i['speed_limit_value']
if "plusstamina" in i['step_type']:
plus_stamina_value[i['position']] = i['plus_stamina_value']
if info['is_beyond']: # beyond判断
dt = info['beyond_health'] - prev_capture
if dt >= step:
curr_capture = prev_capture + step
else:
curr_capture = info['beyond_health']
i = 0
t = prev_capture + step
while i < step_count and t > 0:
dt = capture[i]
if dt > t:
t = 0
else:
t -= dt
i += 1
if i >= step_count:
curr_position = step_count - 1
else:
curr_position = i
else:
i = prev_position
j = prev_capture
t = step
while t > 0 and i < step_count:
dt = capture[i] - j
if dt > t:
j += t
t = 0
else:
t -= dt
j = 0
i += 1
if i >= step_count:
curr_position = step_count - 1
curr_capture = 0
else:
curr_position = i
curr_capture = j
rewards = []
steps = []
for i in range(prev_position, curr_position+1):
if items[i]:
rewards.append({'position': i, 'items': items[i]})
x = {
"map_id": map_id,
"position": i,
"restrict_ids": restrict_ids[i],
"capture": capture[i],
"reward_bundle": reward_bundle[i],
"restrict_id": restrict_id[i],
"restrict_type": restrict_type[i]
}
if step_type[i]:
x['step_type'] = step_type[i]
if speed_limit_value[i]:
x['speed_limit_value'] = speed_limit_value[i]
if plus_stamina_value[i]:
x['plus_stamina_value'] = plus_stamina_value[i]
steps.append(x)
return rewards, steps, curr_position, curr_capture, info
def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, stamina_multiply=1, fragment_multiply=100, prog_boost_multiply=0):
# 成绩上传后世界模式更新,返回字典
step_times = stamina_multiply * fragment_multiply / \
100 * (prog_boost_multiply+100)/100
exp_times = stamina_multiply * (prog_boost_multiply+100)/100
if prog_boost_multiply != 0:
c.execute('''update user set prog_boost = 0 where user_id = :a''', {
'a': user_id})
c.execute('''delete from world_songplay where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': song_id, 'c': difficulty})
c.execute(
'''select character_id, max_stamina_ts, stamina, is_skill_sealed, is_char_uncapped, is_char_uncapped_override from user where user_id=?''', (user_id,))
x = c.fetchone()
character_id = x[0] if x and x[0] is not None else 0
max_stamina_ts = x[1] if x and x[1] is not None else 0
stamina = x[2] if x and x[2] is not None else 12
is_skill_sealed = x[3] if x and x[3] is not None else 1
skill = False
skill_uncap = False
if not is_skill_sealed:
if x:
skill = True
if x[4] is not None and x[4] == 1:
skill_uncap = True
if x[5] is not None and x[5] == 1:
skill_uncap = False
c.execute('''select frag1,prog1,overdrive1,frag20,prog20,overdrive20,frag30,prog30,overdrive30,skill_id,skill_id_uncap from character where character_id=?''', (character_id,))
x = c.fetchone()
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select level, exp from user_char_full where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
else:
c.execute('''select level, exp from user_char where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
y = c.fetchone()
if y:
level = y[0]
exp = y[1]
else:
level = 1
exp = 0
if x:
flag = server.character.calc_char_value(level, x[0], x[3], x[6])
prog = server.character.calc_char_value(level, x[1], x[4], x[7])
overdrive = server.character.calc_char_value(level, x[2], x[5], x[8])
if x[9] is not None and x[9] != '' and skill:
skill = x[9]
else:
skill = None
if x[10] is not None and x[9] != '' and skill_uncap:
skill_uncap = x[10]
else:
skill_uncap = None
else:
flag = 0
prog = 0
overdrive = 0
skill = None
skill_uncap = None
c.execute('''select current_map from user where user_id = :a''', {
'a': user_id})
map_id = c.fetchone()[0]
if beyond_gauge == 0: # 是否是beyond挑战
base_step = 2.5 + 2.45*rating**0.5
step = base_step * (prog/50) * step_times
else:
info = get_world_info(map_id)
if clear_type == 0:
base_step = 8/9 + (rating/1.3)**0.5
else:
base_step = 8/3 + (rating/1.3)**0.5
if character_id in info['character_affinity']:
affinity_multiplier = info['affinity_multiplier'][info['character_affinity'].index(
character_id)]
else:
affinity_multiplier = 1
step = base_step * (prog/50) * step_times * affinity_multiplier
c.execute('''select * from user_world where user_id = :a and map_id =:b''',
{'a': user_id, 'b': map_id})
y = c.fetchone()
if y[4] == 1: # 图不可用
return {}
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2])
# Eto和Luna的技能
character_bonus_progress = None
skill_special = ''
if skill_uncap is not None and skill_uncap and skill_uncap in ['eto_uncap', 'luna_uncap', 'ayu_uncap']:
skill_special = skill_uncap
elif skill is not None and skill and skill in ['eto_uncap', 'luna_uncap', 'ayu_uncap']:
skill_special = skill
if skill_special == 'eto_uncap':
# eto觉醒技能获得残片奖励时世界模式进度加7
fragment_flag = False
for i in rewards:
for j in i['items']:
if j['type'] == 'fragment':
fragment_flag = True
break
if fragment_flag:
break
if fragment_flag:
character_bonus_progress = ETO_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
elif skill_special == 'luna_uncap':
# luna觉醒技能限制格开始时世界模式进度加7
if 'restrict_id' in steps[0] and 'restrict_type' in steps[0] and steps[0]['restrict_type'] != '' and steps[0]['restrict_id'] != '':
character_bonus_progress = LUNA_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
elif skill_special == 'ayu_uncap':
# ayu觉醒技能世界模式进度+5或-5但不会小于0
if random.random() >= 0.5:
character_bonus_progress = AYU_UNCAP_BONUS_PROGRESS
else:
character_bonus_progress = -AYU_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
if step < 0:
character_bonus_progress += step / step_times
step = 0
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
for i in rewards: # 物品分发
for j in i['items']:
amount = j['amount'] if 'amount' in j else 1
item_id = j['id'] if 'id' in j else ''
server.item.claim_user_item(c, user_id, item_id, j['type'], amount)
if 'step_type' in steps[-1]:
if 'plusstamina' in steps[-1]['step_type'] and 'plus_stamina_value' in steps[-1]:
# 体力格子
max_stamina_ts, stamina = add_stamina(
c, user_id, int(steps[-1]['plus_stamina_value']))
# 角色升级
if not Config.CHARACTER_FULL_UNLOCK:
exp, level = server.character.calc_level_up(
c, user_id, character_id, exp, exp_times*rating*6)
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
else:
exp = server.character.LEVEL_STEPS[level]
if beyond_gauge == 0:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"steps": steps
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
else:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"step_count": len(steps)
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
if character_bonus_progress is not None:
re['character_bonus_progress'] = character_bonus_progress
if stamina_multiply != 1:
re['stamina_multiply'] = stamina_multiply
if fragment_multiply != 100:
re['fragment_multiply'] = fragment_multiply
if prog_boost_multiply != 0:
re['prog_boost_multiply'] = prog_boost_multiply
if curr_position == info['step_count']-1 and info['is_repeatable']: # 循环图判断
curr_position = 0
c.execute('''update user_world set curr_position=:a, curr_capture=:b where user_id=:c and map_id=:d''', {
'a': curr_position, 'b': curr_capture, 'c': user_id, 'd': map_id})
return re
def add_stamina(c, user_id, add_stamina):
# 增添体力返回max_stamina_ts和stamina
now = int(time.time() * 1000)
c.execute(
'''select max_stamina_ts, stamina from user where user_id=?''', (user_id,))
x = c.fetchone()
if x and x[0] is not None and x[1] is not None:
stamina = calc_stamina(x[0], x[1]) + add_stamina
max_stamina_ts = now - \
(stamina-server.info.MAX_STAMINA) * \
server.info.STAMINA_RECOVER_TICK
else:
max_stamina_ts = now
stamina = server.info.MAX_STAMINA
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
return max_stamina_ts, stamina
def buy_stamina_by_fragment(user_id):
# 残片买体力,返回字典和错误码
r = {}
with Connect() as c:
c.execute(
'''select next_fragstam_ts from user where user_id=?''', (user_id,))
x = c.fetchone()
if x:
now = int(time.time() * 1000)
next_fragstam_ts = x[0] if x[0] else 0
if now < next_fragstam_ts:
return {}, 905
next_fragstam_ts = now + 24*3600*1000
max_stamina_ts, stamina = add_stamina(c, user_id, 6)
c.execute('''update user set next_fragstam_ts=?, max_stamina_ts=?, stamina=? where user_id=?''',
(next_fragstam_ts, max_stamina_ts, stamina, user_id))
r = {
"user_id": user_id,
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"next_fragstam_ts": next_fragstam_ts
}
return r, None
def buy_stamina_by_ticket(user_id):
# 源点买体力,返回字典和错误码
r = {}
with Connect() as c:
flag, ticket = server.arcpurchase.buy_item(c, user_id, 50)
if flag:
max_stamina_ts, stamina = add_stamina(c, user_id, 6)
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
r = {
"user_id": user_id,
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"ticket": ticket
}
else:
return None, 501
return r, None

View File

@@ -0,0 +1,232 @@
import hashlib
import time
from server.sql import Connect
import functools
from setting import Config
from flask import jsonify
BAN_TIME = [1, 3, 7, 15, 31]
def arc_login(name: str, password: str, device_id: str, ip: str): # 登录判断
# 查询数据库中的user表验证账号密码返回并记录token多返回个error code和extra
# token采用user_id和时间戳连接后hash生成真的是瞎想的没用bear
# 密码和token的加密方式为 SHA-256
error_code = 108
token = None
with Connect() as c:
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute('''select user_id, password, ban_flag from user where name = :name''', {
'name': name})
x = c.fetchone()
if x is not None:
now = int(time.time() * 1000)
if x[2] is not None and x[2] != '':
# 自动封号检查
ban_timestamp = int(x[2].split(':', 1)[1])
if ban_timestamp > now:
return None, 105, {'remaining_ts': ban_timestamp-now}
if x[1] == '':
# 账号封禁
error_code = 106
elif x[1] == hash_pwd:
user_id = str(x[0])
token = hashlib.sha256(
(user_id + str(now)).encode("utf8")).hexdigest()
c.execute(
'''select login_device from login where user_id = :user_id''', {"user_id": user_id})
y = c.fetchall()
if y:
device_list = []
for i in y:
if i[0]:
device_list.append(i[0])
else:
device_list.append('')
should_delete_num = len(
device_list) + 1 - Config.LOGIN_DEVICE_NUMBER_LIMIT
if not Config.ALLOW_LOGIN_SAME_DEVICE:
if device_id in device_list: # 对相同设备进行删除
c.execute('''delete from login where login_device=:a and user_id=:b''', {
'a': device_id, 'b': user_id})
should_delete_num = len(
device_list) + 1 - device_list.count(device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT
if should_delete_num >= 1: # 删掉多余token
if not Config.ALLOW_LOGIN_SAME_DEVICE and Config.ALLOW_BAN_MULTIDEVICE_USER_AUTO: # 自动封号检查
c.execute(
'''select count(*) from login where user_id=? and login_time>?''', (user_id, now-86400000))
if c.fetchone()[0] >= Config.LOGIN_DEVICE_NUMBER_LIMIT:
remaining_ts = arc_auto_ban(c, user_id, now)
return None, 105, {'remaining_ts': remaining_ts}
c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''',
{'user_id': user_id, 'a': int(should_delete_num)})
c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
error_code = None
else:
# 密码错误
error_code = 104
else:
# 用户名错误
error_code = 104
return token, error_code, None
def arc_register(name: str, password: str, device_id: str, email: str, ip: str): # 注册
# 账号注册记录hash密码、用户名和邮箱生成user_id和user_code自动登录返回token
# token和密码的处理同登录部分
def build_user_code(c):
# 生成9位的user_code用的自然是随机
import random
flag = True
while flag:
user_code = ''.join([str(random.randint(0, 9)) for i in range(9)])
c.execute('''select exists(select * from user where user_code = :user_code)''',
{'user_code': user_code})
if c.fetchone() == (0,):
flag = False
return user_code
def build_user_id(c):
# 生成user_id往后加1
c.execute('''select max(user_id) from user''')
x = c.fetchone()
if x[0] is not None:
return x[0] + 1
else:
return 2000001
def insert_user_char(c, user_id):
# 为用户添加初始角色
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 0, 1, 0, 0, 0))
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 1, 1, 0, 0, 0))
c.execute('''select character_id, max_level, is_uncapped from character''')
x = c.fetchall()
if x:
for i in x:
exp = 25000 if i[1] == 30 else 10000
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(user_id, i[0], i[1], exp, i[2], 0))
user_id = None
token = None
error_code = 108
with Connect() as c:
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if c.fetchone() == (0,):
c.execute(
'''select exists(select * from user where email = :email)''', {'email': email})
if c.fetchone() == (0,):
user_code = build_user_code(c)
user_id = build_user_id(c)
now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email)
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email)
''', {'user_code': user_code, 'user_id': user_id, 'join_date': now, 'name': name, 'password': hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': email})
c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id})
token = hashlib.sha256(
(str(user_id) + str(now)).encode("utf8")).hexdigest()
c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
insert_user_char(c, user_id)
error_code = 0
else:
error_code = 102
else:
error_code = 101
return user_id, token, error_code
def token_get_id(token: str):
# 用token获取id没有考虑不同用户token相同情况说不定会有bug
user_id = None
with Connect() as c:
c.execute('''select user_id from login where access_token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def code_get_id(user_code):
# 用user_code获取id
user_id = None
with Connect() as c:
c.execute('''select user_id from user where user_code = :a''',
{'a': user_code})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def auth_required(request):
# arcaea登录验证写成了修饰器
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
user_id = None
headers = request.headers
if 'AppVersion' in headers: # 版本检查
if Config.ALLOW_APPVERSION:
if headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return jsonify({"success": False, "error_code": 1203})
if 'Authorization' in headers:
token = headers['Authorization']
token = token[7:]
user_id = token_get_id(token)
if user_id is not None:
return view(user_id, *args, **kwargs)
else:
return jsonify({"success": False, "error_code": 108})
return wrapped_view
return decorator
def arc_auto_ban(c, user_id, now):
# 多设备自动封号机制,返回封号时长
c.execute('''delete from login where user_id=?''', (user_id, ))
c.execute('''select ban_flag from user where user_id=?''', (user_id,))
x = c.fetchone()
if x and x[0] != '' and x[0] is not None:
last_ban_time = int(x[0].split(':', 1)[0])
i = 0
while i < len(BAN_TIME) - 1 and BAN_TIME[i] <= last_ban_time:
i += 1
ban_time = BAN_TIME[i]
else:
ban_time = BAN_TIME[0]
ban_flag = ':'.join((str(ban_time), str(now + ban_time*24*60*60*1000)))
c.execute('''update user set ban_flag=? where user_id=?''',
(ban_flag, user_id))
return ban_time*24*60*60*1000

View File

@@ -0,0 +1,256 @@
from setting import Config
from server.sql import Connect
import server.info
import server.item
import server.setme
LEVEL_STEPS = {1: 0, 2: 50, 3: 100, 4: 150, 5: 200, 6: 300, 7: 450, 8: 650, 9: 900, 10: 1200, 11: 1600, 12: 2100, 13: 2700, 14: 3400, 15: 4200, 16: 5100,
17: 6100, 18: 7200, 19: 8500, 20: 10000, 21: 11500, 22: 13000, 23: 14500, 24: 16000, 25: 17500, 26: 19000, 27: 20500, 28: 22000, 29: 23500, 30: 25000}
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def get_level_steps():
# 返回level_steps字典数组
return [{'level': i, 'level_exp': LEVEL_STEPS[i]} for i in LEVEL_STEPS]
def calc_char_value(level, value1, value20, value30):
# 计算搭档数值的核心函数,返回浮点数
def calc_char_value_20(level, stata, statb, lva=1, lvb=20):
# 计算1~20级搭档数值的核心函数返回浮点数来自https://redive.estertion.win/arcaea/calc/
n = [0, 0, 0.0005831753900000081, 0.004665403120000065, 0.015745735529959858, 0.03732322495992008, 0.07289692374980007, 0.12596588423968, 0.2000291587694801, 0.29858579967923987, 0.42513485930893946,
0.5748651406910605, 0.7014142003207574, 0.7999708412305152, 0.8740341157603029, 0.9271030762501818, 0.962676775040091, 0.9842542644700301, 0.9953345968799998, 0.9994168246100001, 1]
e = n[lva] - n[lvb]
a = stata - statb
r = a / e
d = stata - n[lva] * r
return d + r * n[level]
def calc_char_value_30(level, stata, statb, lva=20, lvb=30):
# 计算21~30级搭档数值返回浮点数
return (level - lva) * (statb - stata) / (lvb - lva) + stata
if level < 1 or level > 30:
return 0
elif 1 <= level <= 20:
return calc_char_value_20(level, value1, value20)
else:
return calc_char_value_30(level, value20, value30)
def get_char_core(c, character_id):
# 得到对应角色觉醒所需的核心,返回字典列表
r = []
c.execute(
'''select item_id, amount from char_item where character_id = ? and type="core"''', (character_id,))
x = c.fetchall()
if x:
for i in x:
r.append({'core_type': i[0], 'amount': i[1]})
return r
def get_user_characters(c, user_id):
# 获取用户所拥有角色,返回列表
c.execute('''select character_id from user_char where user_id = :user_id''',
{'user_id': user_id})
x = c.fetchall()
characters = []
if x:
for i in x:
characters.append(i[0])
return characters
def get_user_character(c, user_id):
# 得到用户拥有的角色列表,返回列表
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select * from user_char_full a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
else:
c.execute('''select * from user_char a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
x = c.fetchall()
if not x and not Config.CHARACTER_FULL_UNLOCK:
# 添加初始角色
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 0, 1, 0, 0, 0))
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 1, 1, 0, 0, 0))
c.execute('''select * from user_char a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
x = c.fetchall()
if not x:
return []
r = []
for i in x:
char = {
"is_uncapped_override": int2b(i[5]),
"is_uncapped": int2b(i[4]),
"uncap_cores": get_char_core(c, i[1]),
"char_type": i[22],
"skill_id_uncap": i[21],
"skill_requires_uncap": int2b(i[20]),
"skill_unlock_level": i[19],
"skill_id": i[18],
"overdrive": calc_char_value(i[2], i[11], i[14], i[17]),
"prog": calc_char_value(i[2], i[10], i[13], i[16]),
"frag": calc_char_value(i[2], i[9], i[12], i[15]),
"level_exp": LEVEL_STEPS[i[2]],
"exp": i[3],
"level": i[2],
"name": i[7],
"character_id": i[1]
}
if i[1] == 21:
char["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
r.append(char)
return r
def get_one_character(c, user_id, character_id):
# 得到用户某个拥有的角色列表,返回字典
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select * from user_char_full a,character b where a.user_id = :user_id and a.character_id=b.character_id and a.character_id=:a''',
{'user_id': user_id, 'a': character_id})
else:
c.execute('''select * from user_char a,character b where a.user_id = :user_id and a.character_id=b.character_id and a.character_id=:a''',
{'user_id': user_id, 'a': character_id})
x = c.fetchone()
if not x:
return {}
r = {
"is_uncapped_override": int2b(x[5]),
"is_uncapped": int2b(x[4]),
"uncap_cores": get_char_core(c, x[1]),
"char_type": x[22],
"skill_id_uncap": x[21],
"skill_requires_uncap": int2b(x[20]),
"skill_unlock_level": x[19],
"skill_id": x[18],
"overdrive": calc_char_value(x[2], x[11], x[14], x[17]),
"prog": calc_char_value(x[2], x[10], x[13], x[16]),
"frag": calc_char_value(x[2], x[9], x[12], x[15]),
"level_exp": LEVEL_STEPS[x[2]],
"exp": x[3],
"level": x[2],
"name": x[7],
"character_id": x[1]
}
if x[1] == 21:
r["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
return r
def calc_level_up(c, user_id, character_id, exp, exp_addition):
# 计算角色升级,返回当前经验和等级
exp += exp_addition
if exp >= LEVEL_STEPS[20]: # 未觉醒溢出
c.execute('''select is_uncapped from user_char where user_id=? and character_id=?''',
(user_id, character_id))
x = c.fetchone()
if x and x[0] == 0:
return LEVEL_STEPS[20], 20
a = []
b = []
for i in LEVEL_STEPS:
a.append(i)
b.append(LEVEL_STEPS[i])
if exp >= b[-1]: # 溢出
return b[-1], a[-1]
if exp < b[0]: # 向下溢出,是异常状态
return 0, 1
i = len(a) - 1
while exp < b[i]:
i -= 1
return exp, a[i]
def char_use_core(user_id, character_id, amount):
# 以太之滴升级返回user_idcore状态角色状态的字典
r = None
with Connect() as c:
c.execute(
'''select amount from user_item where user_id=? and item_id="core_generic" and type="core"''', (user_id,))
x = c.fetchone()
if x:
pre_amount = x[0]
else:
pre_amount = 0
if amount <= pre_amount:
c.execute(
'''select exp from user_char where user_id=? and character_id=?''', (user_id, character_id))
x = c.fetchone()
if x:
exp, level = calc_level_up(
c, user_id, character_id, x[0], amount*server.info.CORE_EXP)
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
server.item.claim_user_item(
c, user_id, 'core_generic', 'core', -amount)
r = {'character': [get_one_character(c, user_id, character_id)]}
r['cores'] = server.item.get_user_cores(c, user_id)
r['user_id'] = user_id
return r
def char_uncap(user_id, character_id):
# 角色觉醒返回user_idcore状态角色状态的字典
r = None
with Connect() as c:
c.execute('''select * from char_item where character_id=?''',
(character_id,))
x = c.fetchall()
if not x:
return None
success = True
for i in x:
c.execute(
'''select amount from user_item where user_id=? and item_id=? and type=?''', (user_id, i[1], i[2]))
y = c.fetchone()
if not y or i[3] > y[0]:
success = False
break
if success:
c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
(user_id, character_id))
for i in x:
server.item.claim_user_item(c, user_id, i[1], i[2], -i[3])
r = {'character': [get_one_character(c, user_id, character_id)]}
r['cores'] = server.item.get_user_cores(c, user_id)
r['user_id'] = user_id
return r

View File

@@ -0,0 +1,259 @@
from server.sql import Connect
import server.arcworld
import server.arcpurchase
import server.arcdownload
import server.character
import server.item
import time
from setting import Config
MAX_STAMINA = 12
STAMINA_RECOVER_TICK = 1800000
CORE_EXP = 250
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def get_recent_score(c, user_id):
# 得到用户最近一次的成绩,返回列表
c.execute('''select * from user where user_id = :x''', {'x': user_id})
x = c.fetchone()
if x is not None:
if x[11] is not None:
c.execute('''select best_clear_type from best_score where user_id=:u and song_id=:s and difficulty=:d''', {
'u': user_id, 's': x[11], 'd': x[12]})
y = c.fetchone()
if y is not None:
best_clear_type = y[0]
else:
best_clear_type = x[21]
return [{
"rating": x[22],
"modifier": x[19],
"time_played": x[20],
"health": x[18],
"best_clear_type": best_clear_type,
"clear_type": x[21],
"miss_count": x[17],
"near_count": x[16],
"perfect_count": x[15],
"shiny_perfect_count": x[14],
"score": x[13],
"difficulty": x[12],
"song_id": x[11]
}]
return []
def get_user_friend(c, user_id):
# 得到用户的朋友列表,返回列表
c.execute('''select user_id_other from friend where user_id_me = :user_id''', {
'user_id': user_id})
x = c.fetchall()
s = []
if x != [] and x[0][0] is not None:
for i in x:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': i[0], 'y': user_id})
if c.fetchone() == (1,):
is_mutual = True
else:
is_mutual = False
c.execute('''select * from user where user_id = :x''', {'x': i[0]})
y = c.fetchone()
if y is not None:
character = y[6]
is_char_uncapped = int2b(y[8])
is_char_uncapped_override = int2b(y[9])
if y[23] != -1:
character = y[23]
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id=:a and character_id=:b''', {
'a': i[0], 'b': character})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id=:a and character_id=:b''', {
'a': i[0], 'b': character})
z = c.fetchone()
if z:
is_char_uncapped = int2b(z[0])
is_char_uncapped_override = int2b(z[1])
rating = y[5]
if int2b(y[10]):
rating = -1
s.append({
"is_mutual": is_mutual,
"is_char_uncapped_override": is_char_uncapped_override,
"is_char_uncapped": is_char_uncapped,
"is_skill_sealed": int2b(y[7]),
"rating": rating,
"join_date": int(y[3]),
"character": character,
"recent_score": get_recent_score(c, i[0]),
"name": y[1],
"user_id": i[0]
})
s.sort(key=lambda item: item["recent_score"][0]["time_played"] if len(
item["recent_score"]) > 0 else 0, reverse=True)
return s
def get_user_me(c, user_id):
# 构造user/me的数据返回字典
c.execute('''select * from user where user_id = :x''', {'x': user_id})
x = c.fetchone()
r = {}
if x is not None:
user_character = server.character.get_user_character(c, user_id)
# 下面没有使用get_user_characters函数是为了节省一次查询
characters = []
for i in user_character:
characters.append(i['character_id'])
character_id = x[6]
if character_id not in characters:
character_id = 0
c.execute(
'''update user set character_id=0 where user_id=?''', (user_id,))
favorite_character_id = x[23]
if favorite_character_id not in characters: # 这是考虑有可能favourite_character设置了用户未拥有的角色
favorite_character_id = -1
c.execute(
'''update user set favorite_character=-1 where user_id=?''', (user_id,))
# 计算世界排名
c.execute(
'''select world_rank_score from user where user_id=?''', (user_id,))
y = c.fetchone()
if y and y[0]:
c.execute(
'''select count(*) from user where world_rank_score > ?''', (y[0],))
y = c.fetchone()
if y and y[0] + 1 <= Config.WORLD_RANK_MAX:
world_rank = y[0] + 1
else:
world_rank = 0
else:
world_rank = 0
# 源点强化
prog_boost = 0
if x[27] and x[27] != 0:
prog_boost = 300
# 体力计算
next_fragstam_ts = -1
max_stamina_ts = 1586274871917
stamina = 12
if x[31]:
next_fragstam_ts = x[31]
if x[32]:
max_stamina_ts = x[32]
if x[33]:
stamina = x[33]
r = {"is_aprilfools": Config.IS_APRILFOOLS,
"curr_available_maps": server.arcworld.get_available_maps(),
"character_stats": user_character,
"friends": get_user_friend(c, user_id),
"settings": {
"favorite_character": favorite_character_id,
"is_hide_rating": int2b(x[10]),
"max_stamina_notification_enabled": int2b(x[24])
},
"user_id": user_id,
"name": x[1],
"user_code": x[4],
"display_name": x[1],
"ticket": x[26],
"character": character_id,
"is_locked_name_duplicate": False,
"is_skill_sealed": int2b(x[7]),
"current_map": x[25],
"prog_boost": prog_boost,
"next_fragstam_ts": next_fragstam_ts,
"max_stamina_ts": max_stamina_ts,
"stamina": server.arcworld.calc_stamina(max_stamina_ts, stamina),
"world_unlocks": server.item.get_user_items(c, user_id, 'world_unlock'),
"world_songs": server.item.get_user_items(c, user_id, 'world_song'),
"singles": server.item.get_user_items(c, user_id, 'single'),
"packs": server.item.get_user_items(c, user_id, 'pack'),
"characters": characters,
"cores": server.item.get_user_cores(c, user_id),
"recent_score": get_recent_score(c, user_id),
"max_friend": 50,
"rating": x[5],
"join_date": int(x[3]),
"global_rank": world_rank
}
return r
def arc_aggregate_small(user_id):
# 返回用户数据
r = {"success": False}
with Connect() as c:
r = {"success": True,
"value": [{
"id": 0,
"value": get_user_me(c, user_id)
}]}
return r
def arc_aggregate_big(user_id):
# 返回比较全的用户数据
r = {"success": False}
with Connect() as c:
# 防止数据库锁
id_2 = server.arcdownload.get_all_songs(user_id, url_flag=False)
id_5 = {
"current_map": server.arcworld.get_current_map(user_id),
"user_id": user_id,
"maps": server.arcworld.get_world_all(user_id)
}
r = {"success": True,
"value": [{
"id": 0,
"value": get_user_me(c, user_id)
}, {
"id": 1,
"value": server.arcpurchase.get_purchase(c, 'pack')
}, {
"id": 2,
"value": id_2
}, {
"id": 3,
"value": {
"max_stamina": MAX_STAMINA,
"stamina_recover_tick": STAMINA_RECOVER_TICK,
"core_exp": CORE_EXP,
"curr_ts": int(time.time()*1000),
"level_steps": server.character.get_level_steps(),
"world_ranking_enabled": True,
"is_byd_chapter_unlocked": True
}
}, {
"id": 4,
"value": server.arcpurchase.get_user_present(c, user_id)
}, {
"id": 5,
"value": id_5
}
]}
return r

View File

@@ -0,0 +1,98 @@
import os
from shutil import copy, copy2
from server.sql import Connect
from database.database_initialize import main, ARCAEA_SERVER_VERSION
from web.system import update_database
def try_rename(path, new_path):
# 尝试重命名文件,并尝试避免命名冲突,返回最终路径
final_path = new_path
if os.path.exists(new_path):
i = 1
while os.path.exists(new_path + str(i)):
i += 1
os.rename(path, new_path + str(i))
final_path = new_path + str(i)
else:
os.rename(path, new_path)
return final_path
def check_before_run(app):
# 运行前检查关键文件,返回布尔值,其实是因为有人经常忘了
f = True
if not os.path.exists('setting.py'):
app.logger.warning('File `setting.py` is missing.')
f = False
if not os.path.exists('database'):
app.logger.warning('Folder `database` is missing.')
f = False
if not os.path.exists('database/songs'):
app.logger.warning('Folder `database/songs` is missing.')
f = False
if not os.path.exists('database/arcaea_database.db'):
app.logger.warning('File `database/arcaea_database.db` is missing.')
f = False
try:
app.logger.info(
'Try to new the file `database/arcaea_database.db`.')
main('./database/')
app.logger.info(
'Success to new the file `database/arcaea_database.db`.')
f = True
except:
app.logger.warning(
'Fail to new the file `database/arcaea_database.db`.')
else:
with Connect() as c:
try:
c.execute('''select value from config where id="version"''')
x = c.fetchone()
except:
x = None
# 数据库自动更新,不强求
if not x or x[0] != ARCAEA_SERVER_VERSION:
app.logger.warning(
'Maybe the file `database/arcaea_database.db` is an old version.')
try:
app.logger.info(
'Try to update the file `database/arcaea_database.db`.')
path = try_rename('database/arcaea_database.db',
'database/arcaea_database.db.bak')
try:
copy2(path, 'database/arcaea_database.db')
except:
copy(path, 'database/arcaea_database.db')
if os.path.isfile("database/old_arcaea_database.db"):
os.remove('database/old_arcaea_database.db')
try_rename('database/arcaea_database.db',
'database/old_arcaea_database.db')
main('./database/')
update_database()
app.logger.info(
'Success to update the file `database/arcaea_database.db`.')
except:
app.logger.warning(
'Fail to update the file `database/arcaea_database.db`.')
if not os.path.exists('database/arcsong.db'):
app.logger.warning('File `database/arcsong.db` is missing.')
f = False
return f

View File

@@ -0,0 +1,106 @@
from setting import Config
def get_user_items(c, user_id, item_type):
# 得到用户的物品,返回列表,不包含数量信息
if item_type == 'world_song' and Config.WORLD_SONG_FULL_UNLOCK or item_type == 'world_unlock' and Config.WORLD_SCENERY_FULL_UNLOCK:
c.execute('''select item_id from item where type=?''', (item_type,))
else:
c.execute('''select item_id from user_item where user_id = :user_id and type = :item_type''',
{'user_id': user_id, 'item_type': item_type})
x = c.fetchall()
if not x:
return []
re = []
for i in x:
re.append(i[0])
return re
def get_user_cores(c, user_id):
# 得到用户的core返回字典列表
r = []
c.execute(
'''select item_id, amount from user_item where user_id = ? and type="core"''', (user_id,))
x = c.fetchall()
if x:
for i in x:
if i[1]:
amount = i[1]
else:
amount = 0
r.append({'core_type': i[0], 'amount': amount})
return r
def claim_user_item(c, user_id, item_id, item_type, amount=1):
# 处理用户物品,包括添加和删除操作,返回成功与否布尔值
# 这个接口允许memory变成负数所以不能用来购买
try:
amount = int(amount)
except:
return False
if item_type not in ['memory', 'character']:
c.execute('''select is_available from item where item_id=? and type=?''',
(item_id, item_type))
x = c.fetchone()
if x:
if x[0] == 0:
return False
else:
return False
if item_type == 'core':
c.execute(
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (user_id, item_id))
x = c.fetchone()
if x:
if x[0] + amount < 0: # 数量不足
return False
c.execute('''update user_item set amount=? where user_id=? and item_id=? and type="core"''',
(x[0]+amount, user_id, item_id))
else:
if amount < 0: # 添加数量错误
return False
c.execute('''insert into user_item values(?,?,"core",?)''',
(user_id, item_id, amount))
elif item_type == 'memory':
c.execute('''select ticket from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
c.execute('''update user set ticket=? where user_id=?''',
(x[0]+amount, user_id))
else:
return False
elif item_type == 'character':
if not item_id.isdigit():
c.execute(
'''select character_id from character where name=?''', (item_id,))
x = c.fetchone()
if x:
character_id = x[0]
else:
return False
else:
character_id = int(item_id)
c.execute(
'''select exists(select * from user_char where user_id=? and character_id=?)''', (user_id, character_id))
if c.fetchone() == (0,):
c.execute('''insert into user_char values(?,?,1,0,0,0)''',
(user_id, character_id))
elif item_type in ['world_song', 'world_unlock', 'single', 'pack']:
c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
(user_id, item_id, item_type))
if c.fetchone() == (0,):
c.execute('''insert into user_item values(:a,:b,:c,1)''',
{'a': user_id, 'b': item_id, 'c': item_type})
return True

View File

@@ -0,0 +1,159 @@
from server.sql import Connect
from setting import Config
import server.info
import server.character
def b2int(x):
# int与布尔值转换
if x:
return 1
else:
return 0
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def change_char(user_id, character_id, skill_sealed):
# 角色改变,包括技能封印的改变,返回成功与否的布尔值
re = False
with Connect() as c:
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
x = c.fetchone()
if x:
is_uncapped = x[0]
is_uncapped_override = x[1]
else:
return False
if skill_sealed == 'false':
skill_sealed = False
else:
skill_sealed = True
c.execute('''update user set is_skill_sealed = :a, character_id = :b, is_char_uncapped = :c, is_char_uncapped_override = :d where user_id = :e''', {
'a': b2int(skill_sealed), 'b': character_id, 'c': is_uncapped, 'd': is_uncapped_override, 'e': user_id})
re = True
return re
def change_char_uncap(user_id, character_id):
# 角色觉醒改变,返回字典
r = None
with Connect() as c:
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
x = c.fetchone()
if x is not None and x[0] == 1:
c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
'a': b2int(x[1] == 0), 'b': user_id})
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''update user_char set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': b2int(x[1] == 0), 'b': user_id, 'c': character_id})
c.execute(
'''select * from user_char a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''', (user_id, character_id))
else:
c.execute('''update user_char_full set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': b2int(x[1] == 0), 'b': user_id, 'c': character_id})
c.execute(
'''select * from user_char_full a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''', (user_id, character_id))
y = c.fetchone()
if y is not None:
r = {
"is_uncapped_override": int2b(y[5]),
"is_uncapped": int2b(y[4]),
"uncap_cores": server.character.get_char_core(c, y[1]),
"char_type": y[22],
"skill_id_uncap": y[21],
"skill_requires_uncap": int2b(y[20]),
"skill_unlock_level": y[19],
"skill_id": y[18],
"overdrive": server.character.calc_char_value(y[2], y[11], y[14], y[17]),
"prog": server.character.calc_char_value(y[2], y[10], y[13], y[16]),
"frag": server.character.calc_char_value(y[2], y[9], y[12], y[15]),
"level_exp": server.character.LEVEL_STEPS[y[2]],
"exp": y[3],
"level": y[2],
"name": y[7],
"character_id": y[1]
}
return r
def arc_sys_set(user_id, value, set_arg):
# 三个设置PTT隐藏、体力满通知、最爱角色无返回
with Connect() as c:
if 'favorite_character' in set_arg:
value = int(value)
c.execute('''update user set favorite_character = :a where user_id = :b''', {
'a': value, 'b': user_id})
else:
if value == 'false':
value = False
else:
value = True
if 'is_hide_rating' in set_arg:
c.execute('''update user set is_hide_rating = :a where user_id = :b''', {
'a': b2int(value), 'b': user_id})
if 'max_stamina_notification_enabled' in set_arg:
c.execute('''update user set max_stamina_notification_enabled = :a where user_id = :b''', {
'a': b2int(value), 'b': user_id})
return None
def arc_add_friend(user_id, friend_id):
# 加好友返回好友列表或者是错误码602、604
if user_id == friend_id:
return 604
r = None
with Connect() as c:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': user_id, 'y': friend_id})
if c.fetchone() == (0,):
c.execute('''insert into friend values(:a, :b)''',
{'a': user_id, 'b': friend_id})
r = server.info.get_user_friend(c, user_id)
else:
r = 602
return r
def arc_delete_friend(user_id, friend_id):
# 删好友,返回好友列表
r = None
with Connect() as c:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': user_id, 'y': friend_id})
if c.fetchone() == (1,):
c.execute('''delete from friend where user_id_me = :x and user_id_other = :y''',
{'x': user_id, 'y': friend_id})
r = server.info.get_user_friend(c, user_id)
return r

View File

@@ -0,0 +1,83 @@
import sqlite3
from flask import current_app
import traceback
class Connect():
# 数据库连接类,上下文管理
def __init__(self, file_path='./database/arcaea_database.db'):
"""
数据库连接默认连接arcaea_database.db
接受:文件路径
返回sqlite3连接操作对象
"""
self.file_path = file_path
def __enter__(self):
self.conn = sqlite3.connect(self.file_path)
self.c = self.conn.cursor()
return self.c
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conn:
self.conn.commit()
self.conn.close()
if exc_type is not None:
current_app.logger.error(
traceback.format_exception(exc_type, exc_val, exc_tb))
return True
class Sql():
@staticmethod
def select(c, table_name, target_column=[], query=None):
# 执行查询单句sql语句返回fetchall数据
# 使用准确查询,且在单表内
sql = 'select '
sql_dict = {}
if len(target_column) >= 2:
sql += target_column[0]
for i in range(1, len(target_column)):
sql += ',' + target_column[i]
sql += ' from ' + table_name
elif len(target_column) == 1:
sql += target_column[0] + ' from ' + table_name
else:
sql += '* from ' + table_name
where_field = []
where_value = []
if query:
for i in query.query:
where_field.append(i)
where_value.append(query.query[i])
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 2:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
if query and query.sort:
sql += ' order by ' + \
query.sort[0]['column'] + ' ' + query.sort[0]['order']
for i in range(1, len(query.sort)):
sql += ', ' + query.sort[i]['column'] + \
' ' + query.sort[i]['order']
if query and query.limit >= 0:
sql += ' limit :limit offset :offset'
sql_dict['limit'] = query.limit
sql_dict['offset'] = query.offset
c.execute(sql, sql_dict)
return c.fetchall()

253
latest version/setting.py Normal file
View File

@@ -0,0 +1,253 @@
class Config():
'''
This is the setting file. You can change some parameters here.
'''
'''
--------------------
主机的地址和端口号
Host and port of your server
'''
HOST = '0.0.0.0'
PORT = '80'
'''
--------------------
'''
'''
--------------------
游戏API地址前缀
Game API's URL prefix
'''
GAME_API_PREFIX = '/merikuri/17'
'''
--------------------
'''
'''
--------------------
允许使用的游戏版本,若为空,则默认全部允许
Allowed game versions
If it is blank, all are allowed.
'''
ALLOW_APPVERSION = ['3.5.3', '3.5.3c', '3.11.2', '3.11.2c']
'''
--------------------
'''
'''
--------------------
联机功能的端口号,若为空,则默认不开启联机功能
Port of your link play server
If it is blank, link play will be unavailable.
'''
UDP_PORT = '10900'
'''
--------------------
'''
'''
--------------------
SSL证书路径
留空则使用HTTP
SSL certificate path
If left blank, use HTTP.
'''
SSL_CERT = '' # *.pem
SSL_KEY = '' # *.key
'''
--------------------
'''
'''
--------------------
愚人节模式开关
Switch of April Fool's Day
'''
IS_APRILFOOLS = True
'''
--------------------
'''
'''
--------------------
世界排名的最大显示数量
The largest number of global rank
'''
WORLD_RANK_MAX = 200
'''
--------------------
'''
'''
--------------------
世界模式当前活动图设置
Current available maps in world mode
'''
AVAILABLE_MAP = [] # Ex. ['test', 'test2']
'''
--------------------
'''
'''
--------------------
Web后台管理页面的用户名和密码
Username and password of web background management page
'''
USERNAME = 'admin'
PASSWORD = 'admin'
'''
--------------------
'''
'''
--------------------
Web后台管理页面的session秘钥如果不知道是什么请不要修改
Session key of web background management page
If you don't know what it is, please don't modify it.
'''
SECRET_KEY = '1145141919810'
'''
--------------------
'''
'''
--------------------
API接口完全控制权限Token留空则不使用
API interface full control permission Token
If you don't want to use it, leave it blank.
'''
API_TOKEN = ''
'''
--------------------
'''
'''
--------------------
歌曲下载地址前缀,留空则自动获取
Song download address prefix
If left blank, it will be obtained automatically.
'''
DOWNLOAD_LINK_PREFIX = '' # http://***.com/download/
'''
--------------------
'''
'''
--------------------
玩家歌曲下载的24小时次数限制每个文件算一次
Player's song download limit times in 24 hours, once per file
'''
DOWNLOAD_TIMES_LIMIT = 3000
'''
歌曲下载链接的有效时长,单位:秒
Effective duration of song download link, unit: seconds
'''
DOWNLOAD_TIME_GAP_LIMIT = 1000
'''
--------------------
'''
'''
--------------------
Arcaea登录的最大允许设备数量最小值为1
The maximum number of devices allowed to log in Arcaea, minimum: 1
'''
LOGIN_DEVICE_NUMBER_LIMIT = 1
'''
是否允许同设备多应用共存登录
请注意这个选项设置为True时下一个选项将自动变为False
If logging in from multiple applications on the same device is allowed
Note that when this option is set to True, the next option automatically becomes False
'''
ALLOW_LOGIN_SAME_DEVICE = False
'''
24小时内登陆设备数超过最大允许设备数量时是否自动封号1天、3天、7天、15天、31天
When the number of login devices exceeds the maximum number of devices allowed to log in Arcaea within 24 hours, whether the account will be automatically banned (1 day, 3 days, 7 days, 15 days, 31 days)
'''
ALLOW_BAN_MULTIDEVICE_USER_AUTO = True
'''
--------------------
'''
'''
--------------------
是否记录详细的服务器日志
If recording detailed server logs is enabled
'''
ALLOW_LOG_INFO = False
'''
--------------------
'''
'''
--------------------
用户注册时的默认记忆源点数量
The default amount of memories at the time of user registration
'''
DEFAULT_MEMORIES = 0
'''
--------------------
'''
'''
--------------------
数据库更新时,是否采用最新的角色数据,如果你想采用最新的官方角色数据
注意:如果是,旧的数据将丢失;如果否,某些角色的数据变动将无法同步
If using the latest character data when updating database. If you want to only keep newest official character data, please set it `True`.
Note: If `True`, the old data will be lost; If `False`, the data changes of some characters will not be synchronized.
'''
UPDATE_WITH_NEW_CHARACTER_DATA = True
'''
--------------------
'''
'''
--------------------
是否全解锁搭档
If unlocking all partners is enabled
'''
CHARACTER_FULL_UNLOCK = True
'''
--------------------
'''
'''
--------------------
是否全解锁世界歌曲
If unlocking all world songs is enabled
'''
WORLD_SONG_FULL_UNLOCK = True
'''
--------------------
'''
'''
--------------------
是否全解锁世界场景
If unlocking all world scenerys is enabled
'''
WORLD_SCENERY_FULL_UNLOCK = True
'''
--------------------
'''
'''
--------------------
是否强制使用全解锁云端存档
If forcing full unlocked cloud save is enabled
'''
SAVE_FULL_UNLOCK = False
'''
--------------------
'''
'''
--------------------
是否使用最好的 10 条记录(而不是最近的 30 条记录中较好的 10 条)来计算 PTT
Calculate PTT with best 10 instead of recent best 10
'''
USE_B10_AS_R10 = False
'''
--------------------
'''

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,217 +1,237 @@
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1 {
font-family: serif;
color: #377ba8;
margin: 1rem 0;
}
a {
text-decoration: none;
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: lightgray;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a,
nav ul li span,
header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content>header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content>header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post>header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post>header>div:first-of-type {
flex: auto;
}
.post>header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input,
.content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}
.score-item {
margin-bottom: 10px;
clear: both;
}
.song-title {
font-size: 1.3em;
font-weight: bold;
display: inline-block;
}
.difficulty_pst {
font-size: 0.9em;
background-color: rgb(10, 130, 190);
color: white;
}
.difficulty_prs {
font-size: 0.9em;
background-color: rgb(100, 140, 60);
color: white;
}
.difficulty_ftr {
font-size: 0.9em;
background-color: rgb(80, 25, 75);
color: white;
}
.difficulty_byd {
font-size: 0.9em;
background-color: rgb(130, 35, 40);
color: white;
}
.rank {
font-size: 0.8em;
margin-left: 4px;
padding: 0 4px;
}
.rank_big {
font-size: 1.2em;
margin-left: 4px;
padding: 0 4px;
}
.song-detail {
font-size: 0.8em;
float: right;
text-align: right;
}
.song-score {
font-size: 1.375em;
font-family: Arial;
}
.song-clear-type {
font-size: 0.875em;
padding-left: 0.625em;
}
.title {
font-size: 1.375em;
}
.name {
font-size: 1.12em;
font-weight: bold;
}
.footer{
font-size: 0.5em;
color: grey;
text-align: center;
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1 {
font-family: serif;
color: #377ba8;
margin: 1rem 0;
}
a {
text-decoration: none;
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: lightgray;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a,
nav ul li span,
header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content>header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content>header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post>header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post>header>div:first-of-type {
flex: auto;
}
.post>header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input,
.content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}
.score-item {
margin-bottom: 10px;
clear: both;
}
.song-title {
font-size: 1.3em;
font-weight: bold;
display: inline-block;
}
.difficulty_pst {
font-size: 0.9em;
background-color: rgb(10, 130, 190);
color: white;
}
.difficulty_prs {
font-size: 0.9em;
background-color: rgb(100, 140, 60);
color: white;
}
.difficulty_ftr {
font-size: 0.9em;
background-color: rgb(80, 25, 75);
color: white;
}
.difficulty_byd {
font-size: 0.9em;
background-color: rgb(130, 35, 40);
color: white;
}
.rank {
font-size: 0.8em;
margin-left: 4px;
padding: 0 4px;
}
.rank_big {
font-size: 1.2em;
margin-left: 4px;
padding: 0 4px;
}
.song-detail {
font-size: 0.8em;
float: right;
text-align: right;
}
.song-score {
font-size: 1.375em;
font-family: Arial;
}
.song-clear-type {
font-size: 0.875em;
padding-left: 0.625em;
}
.title {
font-size: 1.375em;
}
.name {
font-size: 1.12em;
font-weight: bold;
}
.footer {
font-size: 0.5em;
color: grey;
text-align: center;
}
.char-item {
margin-bottom: 10px;
clear: both;
}
.char-name {
font-size: 1.3em;
font-weight: bold;
display: inline-block;
}
.char-id {
font-size: 1em;
display: inline-block;
}
.char-num {
font-size: 1em;
}

View File

@@ -1,23 +1,23 @@
<!doctype html>
<title>{% block title %}{% endblock %} - Arcaea Server</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Arcaea Server</h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('index.index') }}">Home</a>
<li><a href="{{ url_for('login.logout') }}">Log Out</a> {% else %}
<li><a href="{{ url_for('login.login') }}">Log In</a> {% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %} {% block content %}{% endblock %}
</section>
<footer id="footer" class="footer">Made by Lost@2020</footer>
<!doctype html>
<title>{% block title %}{% endblock %} - Arcaea Server</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Arcaea Server</h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('index.index') }}">Home</a>
<li><a href="{{ url_for('login.logout') }}">Log Out</a> {% else %}
<li><a href="{{ url_for('login.login') }}">Log In</a> {% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %} {% block content %}{% endblock %}
</section>
<footer id="footer" class="footer">Made by Lost@2020-2021</footer>

View File

@@ -0,0 +1,87 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All characters{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for char in posts %}
<div class="char-item">
<span>Id: </span>
<span class="char-id">{{char['character_id']}}</span>
<br />
<span>Name: </span>
<span class="char-name">{{char['name']}}</span>
<br />
<span>Max level: </span>
<span class="char-num">{{char['max_level']}}</span>
<br />
<span>Frag-level 1: </span>
<span class="char-num">{{char['frag1']}}</span>
<br />
<span>Prog-level 1: </span>
<span class="char-num">{{char['prog1']}}</span>
<br />
<span>Overdrive-level 1: </span>
<span class="char-num">{{char['overdrive1']}}</span>
<br />
<span>Frag-level 20: </span>
<span class="char-num">{{char['frag20']}}</span>
<br />
<span>Prog-level 20: </span>
<span class="char-num">{{char['prog20']}}</span>
<br />
<span>Overdrive-level 20: </span>
<span class="char-num">{{char['overdrive20']}}</span>
<br />
<span>Frag-level 30: </span>
<span class="char-num">{{char['frag30']}}</span>
<br />
<span>Prog-level 30: </span>
<span class="char-num">{{char['prog30']}}</span>
<br />
<span>Overdrive-level 30: </span>
<span class="char-num">{{char['overdrive30']}}</span>
<br />
<span>Skill id: </span>
<span class="char-num">{{char['skill_id']}}</span>
<br />
<span>Skill unlock level: </span>
<span class="char-num">{{char['skill_unlock_level']}}</span>
<br />
<span>Uncapped skill id: </span>
<span class="char-num">{{char['skill_id_uncap']}}</span>
<br />
<span>Is uncapped: </span>
<span class="char-num">{{char['is_uncapped']}}</span>
<br />
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All items{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for item in posts %}
<div class="char-item">
<span>Id: </span>
<span class="name">{{item['item_id']}}</span>
<br />
<span>Type: </span>
<span class="char-num">{{item['type']}}</span>
<br />
<span>Is available: </span>
<span class="char-num">{{item['is_available']}}</span>
<br />
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -1,81 +1,85 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All players{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}
{% for user in posts %}
<div id="user-info">
<div class="name">{{user['name']}}
<span class="rank">UID: {{user['user_id']}}</span>
<span class="rank">User code: {{user['user_code']}}</span>
</div>
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
<div class="ptt">PTT: {{user['rating_ptt']//100 ~ '.' ~ user['rating_ptt']%100}}</div>
<div>
<div>Recent plays: </div>
<div>
<div class="score-item">
<span class="song-title">
{{user['song_id']}}
</span>
{% if user['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif user['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif user['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{user['perfect_count']}}</td>
<td> {{'(' ~ user['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{user['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{user['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{user['score']}}</div>
<div class="song-clear-type">
{% if user['clear_type'] == 3 %}Pure Memory
{% elif user['clear_type'] == 2 %}Full Recall
{% elif user['clear_type'] == 5 %}Hard Clear
{% elif user['clear_type'] == 1 %}Normal Clear
{% elif user['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
</div>
<div class="song-rating">成绩评价 Rating: {{user['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{user['time_played']}}
</div>
</div>
</div>
</div>
</div>
{% if not loop.last %}
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All players{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}
{% for user in posts %}
<div id="user-info">
<div class="name">{{user['name']}}
<span class="rank">UID: {{user['user_id']}}</span>
<span class="rank">User code: {{user['user_code']}}</span>
{% if user['ban_flag'] %}
<span class="rank">Banned</span>
{% endif %}
</div>
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
<div class="ptt">PTT: {{'%0.2f'|format(user['rating_ptt']/100|float)}}</div>
<div class="ptt">Memories: {{user['ticket']}}</div>
<div>
<div>Recent plays: </div>
<div>
<div class="score-item">
<span class="song-title">
{{user['song_id']}}
</span>
{% if user['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif user['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif user['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{user['perfect_count']}}</td>
<td> {{'(' ~ user['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{user['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{user['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{user['score']}}</div>
<div class="song-clear-type">
{% if user['clear_type'] == 3 %}Pure Memory
{% elif user['clear_type'] == 2 %}Full Recall
{% elif user['clear_type'] == 5 %}Hard Clear
{% elif user['clear_type'] == 1 %}Normal Clear
{% elif user['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
</div>
<div class="song-rating">成绩评价 Rating: {{user['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{user['time_played']}}
</div>
</div>
</div>
</div>
</div>
{% if not loop.last %}
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,55 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All presents{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for present in posts %}
<div class="char-present">
<span>Id: </span>
<span class="name">{{present['present_id']}}</span>
<br />
<span>Expire time: </span>
<span class="char-num">{{present['expire_ts']}}</span>
<br />
<span>Description: </span>
<span class="char-num">{{present['description']}}</span>
<br />
{% if present['items'] %}<br />
{% for x in present['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All purchases{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for item in posts %}
<div class="char-item">
<span>Name: </span>
<span class="name">{{item['purchase_name']}}</span>
<br />
<span>Price: </span>
<span class="char-num">{{item['price']}}</span>
<br />
<span>Original price: </span>
<span class="char-num">{{item['orig_price']}}</span>
<br />
<span>Discount from: </span>
<span class="char-num">{{item['discount_from']}}</span>
<br />
<span>Discount to: </span>
<span class="char-num">{{item['discount_to']}}</span>
<br />
{% if item['items'] %}<br />
{% for x in item['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All redeem codes{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for redeem in posts %}
<div class="char-redeem">
<span>Code: </span>
<span class="name">{{redeem['code']}}</span>
<br />
<span>Type: </span>
<span class="char-num">
{% if redeem['type'] == 0 %}
仅可使用一次 It can only be used once.
{% elif redeem['type'] == 1 %}
每个用户仅可以使用一次 Each user can only use it once.
{% endif %}
</span>
<br />
{% if redeem['items'] %}<br />
{% for x in redeem['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
<div><a href="/web/redeem/{{redeem['code']}}">使用者 Players who have used it.</a></div>
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -1,48 +1,48 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All songs{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for song in posts %}
<div class="score-item">
<span>Sid: </span>
<span class="song-title">{{song['song_id']}}</span>
<br />
<span>Name_en: </span>
<span class="song-title">{{song['name_en']}}</span>
<br />
<div>铺面定数 Chart const: </div>
{% if song['rating_pst'] %}
<span class="difficulty_pst">PST</span>
<span class="song-rating">{{song['rating_pst']}}</span>
{% endif %}
<br />
{% if song['rating_prs'] %}
<span class="difficulty_prs">PRS</span>
<span class="song-rating">{{song['rating_prs']}}</span>
{% endif %}
<br />
{% if song['rating_ftr'] %}
<span class="difficulty_ftr">FTR</span>
<span class="song-rating">{{song['rating_ftr']}}</span>
{% endif %}
<br />
{% if song['rating_byn'] %}
<span class="difficulty_byd">BYD</span>
<span class="song-rating">{{song['rating_byn']}}</span>
{% endif %}
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All songs{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}<br />
{% for song in posts %}
<div class="score-item">
<span>Sid: </span>
<span class="song-title">{{song['song_id']}}</span>
<br />
<span>Name_en: </span>
<span class="song-title">{{song['name_en']}}</span>
<br />
<div>铺面定数 Chart const: </div>
{% if song['rating_pst'] %}
<span class="difficulty_pst">PST</span>
<span class="song-rating">{{song['rating_pst']}}</span>
{% endif %}
<br />
{% if song['rating_prs'] %}
<span class="difficulty_prs">PRS</span>
<span class="song-rating">{{song['rating_prs']}}</span>
{% endif %}
<br />
{% if song['rating_ftr'] %}
<span class="difficulty_ftr">FTR</span>
<span class="song-rating">{{song['rating_ftr']}}</span>
{% endif %}
<br />
{% if song['rating_byn'] %}
<span class="difficulty_byd">BYD</span>
<span class="song-rating">{{song['rating_byn']}}</span>
{% endif %}
</div>
{% if not loop.last %}
<br />
<hr />
<br />
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Ban the user{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/banuser" method="post">
<div class="title">Ban the user</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<div class="content">提示:重设用户密码即可解封账号</div>
<div class="content">Tip: Reset the user password to unseal the account.</div>
<br />
<input type="submit" value="Execute">
</form>
<br />
<hr />
<form action="/web/banuser/deleteuserscore" method="post">
<div class="title">Delete all of the user's scores</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<div class="content">警告!这个操作具有破坏性,且不可恢复。</div>
<div class="content">Warning! This operation is destructive and unrecoverable.</div>
<br />
<input type="submit" value="Execute">
</form>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the characters{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changesong/editchar" method="post">
<div class="title">Edit the character</div>
<label for="id">ID of the character</label>
<input name="id" id="sid" required>
<label for="level">Level</label>
<input name="level" id="level">
<div>
<select name="skill_id">
<option value="">Skill id</option>
{% for skill_id in skill_ids %}
<option value={{ skill_id }}>{{ skill_id }}</option>
{% endfor %}
</select>
</div>
<div>
<select name="skill_id_uncap">
<option value="">Uncapped skill id</option>
{% for skill_id in skill_ids %}
<option value={{ skill_id }}>{{ skill_id }}</option>
{% endfor %}
</select>
</div>
<br />
<div class="content">如果某些不需要修改,留空即可</div>
<div class="content">If some things do not need to be modified, leave it blank.</div>
<div class="content">更新时角色数据表不会同步</div>
<div class="content">The character table will not be synchronized when updated.</div>
<input type="submit" value="Edit">
</form>
<br />
<hr />
<form action="/web/changesong/updatechar" method="post">
<div class="title">Update the character</div>
<div class="content">更新所有用户拥有的角色</div>
<div class="content">Update all users' characters.</div>
<input type="submit" value="Update">
</form>
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the items{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changeitem" method="post">
<div class="title">Add the item</div>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single</option>
<option value='pack'>Pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
</select>
</div>
<br />
<div><label for="is_available">是否可用 Is available:</label>
<br />
<br />
<label><input type="radio" name="is_available" checked value=1>True</label>
<br />
<label><input type="radio" name="is_available" value=0>False</label>
</div>
<br />
<input type="submit" value="Add">
</form>
<form action="/web/changeitem/delete" method="post">
<div class="title">Delete the item</div>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single</option>
<option value='pack'>Pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
</select>
</div>
<br />
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the presents{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changepresent/addpresent" method="post">
<div class="title">Add the present</div>
<label for="present_id">ID of the present</label>
<input name="present_id" id="present_id" required>
<label for="description">Description of the present</label>
<input name="description" id="description" required>
<label for="expire_ts">Expire time</label>
<input type="datetime-local" name="expire_ts" id="expire_ts" required>
<br />
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single song</option>
<option value='world_song'>World song</option>
<option value='world_unlock'>World unlock scene</option>
<option value='character'>Character</option>
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<div class="content">时间填写是一个HTML5控件</div>
<div class="content">Time filling is an HTML5 control.</div>
<input type="submit" value="Add">
</form>
<br />
<hr />
<form action="/web/changepresent/deletepresent" method="post">
<div class="title">Delete the present</div>
<label for="present_id">ID of the present</label>
<input name="present_id" id="present_id" required>
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the purchases{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changepurchase" method="post">
<div class="title">Add the purchase</div>
<label for="purchase_name">Purchase name</label>
<input name="purchase_name" id="purchase_name" required>
<label for="price">Price</label>
<input name="price" id="price">
<label for="orig_price">Original price</label>
<input name="orig_price" id="orig_price">
<label for="discount_from">Discount from</label>
<input type="datetime-local" name="discount_from" id="discount_from">
<label for="discount_to">Discount to</label>
<input type="datetime-local" name="discount_to" id="discount_to">
<div class="content">时间填写是一个HTML5控件</div>
<div class="content">Time filling is an HTML5 control.</div>
<br />
<input type="submit" value="Add">
</form>
<form action="/web/changepurchase/delete" method="post">
<div class="title">Delete the purchase</div>
<label for="purchase_name">Purchase name</label>
<input name="purchase_name" id="purchase_name" required>
<br />
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the purchase's items{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changepurchaseitem" method="post">
<div class="title">Add the purchase's item</div>
<label for="purchase_name">Purchase name</label>
<input name="purchase_name" id="purchase_name" required>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single song</option>
<option value='pack'>Song pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
<option value='core'>Core</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<input type="submit" value="Add">
</form>
<form action="/web/changepurchaseitem/delete" method="post">
<div class="title">Delete the purchase's item</div>
<label for="purchase_name">Purchase name</label>
<input name="purchase_name" id="purchase_name" required>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single</option>
<option value='pack'>Pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
<option value='core'>Core</option>
</select>
</div>
<br />
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the redeem codes{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changeredeem/addredeem" method="post">
<div class="title">Add the redeem</div>
<div>指定生成 Specify generation</div>
<label for="code">The redeem code</label>
<input name="code" id="code">
<div>Or</div>
<div>随机批量生成 Batch generation at random</div>
<label for="redeem_amount">Redeem code amount</label>
<input name="redeem_amount" id="redeem_amount">
<br />
<div>Redeem code type:
<br />
<br />
<label><input type="radio" name="redeem_type" value="0" checked>仅可使用一次 It can only be used once.</label>
<br />
<label><input type="radio" name="redeem_type" value="1">每个用户仅可以使用一次 Each user can only use it once.</label>
</div>
<br />
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single song</option>
<option value='pack'>Song pack</option>
<option value='world_song'>World song</option>
<option value='world_unlock'>World unlock scene</option>
<option value='character'>Character</option>
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<div class="content">兑换码长度为10~20。</div>
<div class="content">The length of redeem code is 10 ~ 20.</div>
<div class="content">随机生成数量不得超过1000。</div>
<div class="content">The amount of batch generation should not be more than 1000.</div>
<input type="submit" value="Add">
</form>
<br />
<hr />
<form action="/web/changeredeem/deleteredeem" method="post">
<div class="title">Delete the redeem</div>
<label for="code">The redeem code</label>
<input name="code" id="code" required>
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the scores{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changescore/delete" method="post">
<div class="title">Delete the scores</div>
<label for="sid">SID of the song</label>
<input name="sid" id="sid">
<label for="difficulty">Difficulty</label>
<select name='difficulty' id='difficulty'>
<option value="-1" selected>All</option>
<option value="0">Past</option>
<option value="1">Present</option>
<option value="2">Future</option>
<option value="3">Beyond</option>
</select>
<br />
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<br />
<div class="content">上述三项是查询参数,如果留空,则被认为是全部,都不填则认为是错误的。</div>
<div class="content">The above three are query parameters. If they are left blank, they are considered all. However,
if you don't fill in all of them, it's wrong.</div>
<br />
<br />
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -1,35 +1,35 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the songs{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changesong/addsong" method="post">
<div class="title">Add the song</div>
<label for="sid">SID of the song</label>
<input name="sid" id="sid" required>
<label for="name_en">English name of the song(Not important but neccessary)</label>
<input name="name_en" id="name_en" required>
<label for="rating_pst">Past chart const</label>
<input name="rating_pst" id="rating_pst" required>
<label for="rating_prs">Present chart const</label>
<input name="rating_prs" id="rating_prs" required>
<label for="rating_ftr">Future chart const</label>
<input name="rating_ftr" id="rating_ftr" required>
<label for="rating_byd">Beyond chart const</label>
<input name="rating_byd" id="rating_byd" required>
<div class="content">如果没有某个面,应该填入-1。</div>
<div class="content">If there is no some chart, fill in -1 please.</div>
<input type="submit" value="Add">
</form>
<br />
<hr />
<form action="/web/changesong/deletesong" method="post">
<div class="title">Delete the song</div>
<label for="sid">SID of the song</label>
<input name="sid" id="sid" required>
<input type="submit" value="Delete">
</form>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change the songs{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changesong/addsong" method="post">
<div class="title">Add the song</div>
<label for="sid">SID of the song</label>
<input name="sid" id="sid" required>
<label for="name_en">English name of the song(Not important but neccessary)</label>
<input name="name_en" id="name_en" required>
<label for="rating_pst">Past chart const</label>
<input name="rating_pst" id="rating_pst" required>
<label for="rating_prs">Present chart const</label>
<input name="rating_prs" id="rating_prs" required>
<label for="rating_ftr">Future chart const</label>
<input name="rating_ftr" id="rating_ftr" required>
<label for="rating_byd">Beyond chart const</label>
<input name="rating_byd" id="rating_byd" required>
<div class="content">如果没有某个面,应该填入-1。</div>
<div class="content">If there is no some chart, fill in -1 please.</div>
<input type="submit" value="Add">
</form>
<br />
<hr />
<form action="/web/changesong/deletesong" method="post">
<div class="title">Delete the song</div>
<label for="sid">SID of the song</label>
<input name="sid" id="sid" required>
<input type="submit" value="Delete">
</form>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change user information{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changeuser/edituser" method="post">
<div class="title">Edit the user</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<br />
<label for="ticket">Memories</label>
<input name="ticket" id="ticket">
<br />
<div class="content">如果某些不需要修改,留空即可</div>
<div class="content">If some things do not need to be modified, leave it blank.</div>
<div class="content">目前只支持修改记忆源点</div>
<div class="content">At present, only memories can be modified.</div>
<input type="submit" value="Edit">
</form>
<br />
<hr />
<form action="/web/changeuser/edituser" method="post">
<div class="title">Edit all the users</div>
<label for="ticket">Memories</label>
<input name="ticket" id="ticket">
<input type="submit" value="Edit all">
</form>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change user purchase information{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changeuserpurchase/edituser" method="post">
<div class="title">Edit the user</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<br />
<div>对所有单曲和曲包的操作 Operation to all singles and packs:
<br />
<br />
<label><input type="radio" name="method" value="0">解锁 Unlock</label>
<br />
<label><input type="radio" name="method" value="1">封绝 Lock</label>
</div>
<br />
<input type="submit" value="Execute">
</form>
<br />
<hr />
<form action="/web/changeuserpurchase/edituser" method="post">
<div class="title">Edit all the users</div>
<br />
<div>对所有单曲和曲包的操作 Operation to all singles and packs:
<br />
<br />
<label><input type="radio" name="method" value="0">解锁 Unlock</label>
<br />
<label><input type="radio" name="method" value="1">封绝 Lock</label>
</div>
<br />
<input type="submit" value="Execute">
</form>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Change user password{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/changeuserpwd" method="post">
<div class="title">Edit the user's password</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<br />
<label for="pwd">Password</label>
<input type="password" name="pwd" id="pwd" required>
<label for="pwd2">Enter again</label>
<input type="password" name="pwd2" id="pwd2" required>
<input type="submit" value="Edit">
</form>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Deliver presents{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/deliverpresent" method="post">
<div class="title">Deliver to the user</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<br />
<label for="present_id">ID of the present</label>
<input name="present_id" id="present_id" required>
<br />
<input type="submit" value="Edit">
</form>
<br />
<hr />
<form action="/web/deliverpresent" method="post">
<div class="title">Deliver to all the users</div>
<label for="present_id">ID of the present</label>
<input name="present_id" id="present_id" required>
<input type="submit" value="Edit all">
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Index{% endblock %}</h1>
{% endblock %}
{% block content %}
<h1>说明 Statement</h1>
<p>这是Arcaea Server的后台管理协助系统可以进行一些简单的操作</br>
This is the background management assistance system of Arcaea Server, which can be used to do some simple
operations.
</p>
<h1>游戏方面 Game</h1>
<a href="{{ url_for('index.single_player_score') }}">单个玩家成绩查询 Single player score</a></br></br>
<a href="{{ url_for('index.single_player_ptt') }}">单个玩家PTT详情查询 Single player ptt</a></br></br>
<a href="{{ url_for('index.all_player') }}">所有玩家信息查询 All players</a></br></br>
<a href="{{ url_for('index.all_song') }}">谱面信息查询 All songs</a></br></br>
<a href="{{ url_for('index.all_character') }}">角色信息查询 All characters</a></br></br>
<a href="{{ url_for('index.all_item') }}">物品信息查询 All items</a></br></br>
<a href="{{ url_for('index.all_purchase') }}">购买信息查询 All purchases</a></br></br>
<a href="{{ url_for('index.all_present') }}">奖励信息查询 All presents</a></br></br>
<a href="{{ url_for('index.all_redeem') }}">兑换码信息查询 All redeem codes</a></br></br>
<a href="{{ url_for('index.single_chart_top') }}">单个铺面排行榜查询 Single song chart tops</a>
<hr>
<h1>系统方面 System</h1>
<a href="{{ url_for('index.update_database') }}">数据库更新 Update databases</a></br></br>
<a href="{{ url_for('index.edit_userpwd') }}">用户密码修改 Change user password</a></br></br>
<a href="{{ url_for('index.ban_user') }}">封禁用户 Ban the user</a></br></br>
<a href="{{ url_for('index.change_score') }}">成绩删除 Delete the scores</a></br></br>
<a href="{{ url_for('index.update_user_save') }}">用户存档更新 Update user saves</a></br></br>
<a href="{{ url_for('index.change_song') }}">歌曲修改 Change the songs</a></br></br>
<a href="{{ url_for('index.change_character') }}">角色修改 Change the characters</a></br></br>
<a href="{{ url_for('index.change_item') }}">物品信息修改 Change the items</a></br></br>
<a href="{{ url_for('index.change_purchase') }}">购买信息修改 Change the purchases</a></br></br>
<a href="{{ url_for('index.change_purchase_item') }}">购买信息的物品修改 Change the purchase items</a></br></br>
<a href="{{ url_for('index.change_user') }}">用户信息修改 Change user information</a></br></br>
<a href="{{ url_for('index.change_user_purchase') }}">用户购买信息修改 Change user purchase information</a></br></br>
<a href="{{ url_for('index.change_present') }}">奖励修改 Change the presents</a></br></br>
<a href="{{ url_for('index.deliver_present') }}">奖励分发 Deliver the presents</a></br></br>
<a href="{{ url_for('index.change_redeem') }}">兑换码修改 Change the redeem codes</a>
{% endblock %}

View File

@@ -1,14 +1,14 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Redeem - {{code}}{% endblock %}</h1>
{% endblock %}
{% block content %}
{% if posts %}
{% for user in posts %}
<div id="user-info">
<div class="name">{{user['name']}}
<span class="rank">UID: {{user['user_id']}}</span>
<span class="rank">User code: {{user['user_code']}}</span>
</div>
</div>
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
<br />
<a href="{{ url_for('index.all_redeem') }}">Back</a>
{% endblock %}

View File

@@ -1,108 +1,108 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single chart top{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="sid">SID of the song</label>
<span>模糊查询,只返回第一个</span>
<span>Fuzzy query,and only return the first one.</span>
<input name="sid" id="sid">
<label for="difficulty">Difficulty</label>
<select name='difficulty' id='difficulty'>
<option value="0" selected>Past</option>
<option value="1">Present</option>
<option value="2">Future</option>
<option value="3">Beyond</option>
</select>
<br />
<input type="submit" value="Find">
</br>
</br>
</br>
{% if song_id %}
<div>
<span class="song-title">
{{song_id}}
</span>
{% if difficulty == 0 %}
<span class="difficulty_pst">PST</span>
{% elif difficulty == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif difficulty == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<br />
<div class="song-title">
{{song_name_en}}
</div>
</div>
{% endif %}
<br />
<hr />
{% for post in posts %}
<div class="score-item">
<div>
<span class='rank_big'>{{'#' ~ post['rank']}}</span>
<span class="name">
{{post['name']}}
</span>
<span class="rank">UID: {{post['user_id']}}</span>
</div>
<div class="song-detail">
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
</br>
{% endif %}
{% endfor %}
</form>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single chart top{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="sid">SID of the song</label>
<span>模糊查询,只返回第一个</span>
<span>Fuzzy query,and only return the first one.</span>
<input name="sid" id="sid">
<label for="difficulty">Difficulty</label>
<select name='difficulty' id='difficulty'>
<option value="0" selected>Past</option>
<option value="1">Present</option>
<option value="2">Future</option>
<option value="3">Beyond</option>
</select>
<br />
<input type="submit" value="Find">
</br>
</br>
</br>
{% if song_id %}
<div>
<span class="song-title">
{{song_id}}
</span>
{% if difficulty == 0 %}
<span class="difficulty_pst">PST</span>
{% elif difficulty == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif difficulty == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<br />
<div class="song-title">
{{song_name_en}}
</div>
</div>
{% endif %}
<br />
<hr />
{% for post in posts %}
<div class="score-item">
<div>
<span class='rank_big'>{{'#' ~ post['rank']}}</span>
<span class="name">
{{post['name']}}
</span>
<span class="rank">UID: {{post['user_id']}}</span>
</div>
<div class="song-detail">
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
</br>
{% endif %}
{% endfor %}
</form>
{% endblock %}

View File

@@ -1,85 +1,85 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single player score{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<input type="submit" value="Find">
</br>
</br>
</br>
{% for post in posts %}
<div class="score-item">
<span class="song-title">
{{post['song_id']}}
</span>
{% if post['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif post['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif post['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{'#' ~ post['rank']}}</span>
<div class="song-detail">
</br>
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-rating">成绩评价 Rating: {{post['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
</br>
{% endif %}
{% endfor %}
</form>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single player score{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<input type="submit" value="Find">
</br>
</br>
</br>
{% for post in posts %}
<div class="score-item">
<span class="song-title">
{{post['song_id']}}
</span>
{% if post['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif post['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif post['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{'#' ~ post['rank']}}</span>
<div class="song-detail">
</br>
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-rating">成绩评价 Rating: {{post['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
</br>
{% endif %}
{% endfor %}
</form>
{% endblock %}

View File

@@ -1,191 +1,195 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single player ptt{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<input type="submit" value="Find">
<br />
<br />
<br />
{% if user %}
<hr />
<div id="user-info">
<div class="title">玩家信息 Player information</div>
<div class="name">{{user['name']}}
<span class="rank">UID: {{user['user_id']}}</span>
<span class="rank">User code: {{user['user_code']}}</span>
</div>
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
<div class="ptt">PTT: {{user['rating_ptt']//100 ~ '.' ~ user['rating_ptt']%100}}</div>
<div class="ptt">Best 30 PTT: {{bestptt}}</div>
<div class="ptt">Recent 10 PTT: {{recentptt}}</div>
<div>
<div>Recent plays: </div>
<div>
<div class="score-item">
<span class="song-title">
{{user['song_id']}}
</span>
{% if user['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif user['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif user['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{user['perfect_count']}}</td>
<td> {{'(' ~ user['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{user['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{user['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{user['score']}}</div>
<div class="song-clear-type">
{% if user['clear_type'] == 3 %}Pure Memory
{% elif user['clear_type'] == 2 %}Full Recall
{% elif user['clear_type'] == 5 %}Hard Clear
{% elif user['clear_type'] == 1 %}Normal Clear
{% elif user['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
</div>
<div class="song-rating">成绩评价 Rating: {{user['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{user['time_played']}}
</div>
</div>
</div>
</div>
</div>
<hr />
{% if posts %}
<div class="title">Best 30</div>
{% endif %}
{% for post in posts %}
<div class="score-item">
<span class="song-title">
{{post['song_id']}}
</span>
{% if post['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif post['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif post['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{'#' ~ post['rank']}}</span>
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-rating">成绩评价 Rating: {{post['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
<hr />
{% if recent %}
<div class="title">Recent 30</div>
{% for i in recent %}
{% if i %}
<div class="score-item">
<span class="song-title">
{{i['song_id']}}
</span>
{% if i['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif i['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif i['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{loop.index}}</span>
<div class="song-rating">成绩评价 Rating: {{i['rating']}}</div>
</div>
{% if not loop.last %}
<br />
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
</form>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Single player ptt{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<input type="submit" value="Find">
<br />
<br />
<br />
{% if user %}
<hr />
<div id="user-info">
<div class="title">玩家信息 Player information</div>
<div class="name">{{user['name']}}
<span class="rank">UID: {{user['user_id']}}</span>
<span class="rank">User code: {{user['user_code']}}</span>
{% if user['ban_flag'] %}
<span class="rank">Banned</span>
{% endif %}
</div>
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
<div class="ptt">Memories: {{user['ticket']}}</div>
<div class="ptt">PTT: {{'%0.2f'|format(user['rating_ptt']/100|float)}}</div>
<div class="ptt">Best 30 PTT: {{bestptt}}</div>
<div class="ptt">Recent 10 PTT: {{recentptt}}</div>
<div>
<div>Recent plays: </div>
<div>
<div class="score-item">
<span class="song-title">
{{user['song_id']}}
</span>
{% if user['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif user['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif user['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{user['perfect_count']}}</td>
<td> {{'(' ~ user['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{user['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{user['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{user['score']}}</div>
<div class="song-clear-type">
{% if user['clear_type'] == 3 %}Pure Memory
{% elif user['clear_type'] == 2 %}Full Recall
{% elif user['clear_type'] == 5 %}Hard Clear
{% elif user['clear_type'] == 1 %}Normal Clear
{% elif user['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
</div>
<div class="song-rating">成绩评价 Rating: {{user['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{user['time_played']}}
</div>
</div>
</div>
</div>
</div>
<hr />
{% if posts %}
<div class="title">Best 30</div>
{% endif %}
{% for post in posts %}
<div class="score-item">
<span class="song-title">
{{post['song_id']}}
</span>
{% if post['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif post['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif post['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{'#' ~ post['rank']}}</span>
<div class="song-detail">
<br />
<table>
<tbody>
<tr>
<td>PURE: </td>
<td>{{post['perfect_count']}}</td>
<td> {{'(' ~ post['shiny_perfect_count'] ~ ')'}}</td>
</tr>
<tr>
<td>FAR: </td>
<td>{{post['near_count']}}</td>
<td></td>
</tr>
<tr>
<td>LOST: </td>
<td>{{post['miss_count']}}</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="song-score">{{post['score']}}</div>
<div class="song-clear-type">
{% if post['clear_type'] == 3 %}Pure Memory
{% elif post['clear_type'] == 2 %}Full Recall
{% elif post['clear_type'] == 5 %}Hard Clear
{% elif post['clear_type'] == 1 %}Normal Clear
{% elif post['clear_type'] == 4 %}Easy Clear
{% else%}Track Lost
{% endif %}
<span class="song-clear-type" style="margin-left: 1.875em;">
{% if post['best_clear_type'] == 3 %}(Pure Memory)
{% elif post['best_clear_type'] == 2 %}(Full Recall)
{% elif post['best_clear_type'] == 5 %}(Hard Clear)
{% elif post['best_clear_type'] == 1 %}(Normal Clear)
{% elif post['best_clear_type'] == 4 %}(Easy Clear)
{% else%}(Track Lost)
{% endif %}
</span>
</div>
<div class="song-rating">成绩评价 Rating: {{post['rating']}}</div>
<div class="song-clear-date">日期 Date:
{{post['time_played']}}
</div>
</div>
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
<hr />
{% if recent %}
<div class="title">Recent 30</div>
{% for i in recent %}
{% if i %}
<div class="score-item">
<span class="song-title">
{{i['song_id']}}
</span>
{% if i['difficulty'] == 0 %}
<span class="difficulty_pst">PST</span>
{% elif i['difficulty'] == 1 %}
<span class="difficulty_prs">PRS</span>
{% elif i['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
<span class='rank'>{{loop.index}}</span>
<div class="song-rating">成绩评价 Rating: {{i['rating']}}</div>
</div>
{% if not loop.last %}
<br />
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Update databases{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post" enctype="multipart/form-data">
<label for="name">Old database</label>
<input type="file" name="file">
<input type="submit" value="Submit">
</form>
<div class="content">
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的全角色列表。<br />
可上传文件: arcaea_database.db和arcsong.db<br />
新数据库不存在的数据会被添加,存在的重复数据也会被改变。<br /><br />
Here you can synchronize the old version of the database to the new version of the database and refresh the list of full
characters owned by players.<br />
Uploadable files: arcaea_database.db & arcsong.db<br />
Data that does not exist in the new database will be added and the existing duplicate data will also be changed.
</div>
<br />
<hr />
<form action="/web/updatedatabase/refreshsonghash" method="post">
<div class="title">Refresh songs' hash</div>
<br />
<input type="submit" value="Refresh">
<div class="content">这里可以刷新储存在数据库中的songs文件夹下所有文件的Hash值。目的是应对歌曲谱面文件的更新。</div>
<div class="content">Here you can refresh the hash values of all the files stored in the songs folder in the
database. The purpose is to deal with the updating of songs' files.</div>
</form>
<br />
<hr />
<form action="/web/updatedatabase/refreshsongrating" method="post">
<div class="title">Refresh songs' rating</div>
<br />
<input type="submit" value="Refresh">
<div class="content">这里可以刷新数据库中所有成绩的评分。目的是应对歌曲谱面定数的更新。</div>
<div class="content">Here you can refresh the ratings of all scores in the database. The purpose is to deal
with the updating of songs' chart consts.</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Update users' saves{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="/web/updateusersave" method="post">
<div class="title">Update one user's saves to the database</div>
<label for="name">Arcaea Username</label>
<input name="name" id="name">
or<br />
<label for="user_code">Arcaea User Code</label>
<input name="user_code" id="user_code">
<br />
<input type="submit" value="Update">
</form>
<br />
<hr />
<form action="/web/updateusersave" method="post">
<div class="title">Update all users' saves to the database</div>
<br />
<input type="submit" value="Update all">
<div class="content">这里会将用户上传的存档覆盖到best_score表中请注意是覆盖不会进行分数比较。</div>
<div class="content">Here you can overlay the saves uploaded by users to best_score table. Please remember that it
is an overlay action, so scores will not be compared.</div>
</form>
{% endblock %}

View File

View File

@@ -0,0 +1,24 @@
import os
from cryptography.hazmat.primitives.ciphers import (
Cipher, algorithms, modes
)
def encrypt(key, plaintext, associated_data):
iv = os.urandom(12)
encryptor = Cipher(
algorithms.AES(key),
modes.GCM(iv, min_tag_length=12),
).encryptor()
encryptor.authenticate_additional_data(associated_data)
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return (iv, ciphertext, encryptor.tag)
def decrypt(key, associated_data, iv, ciphertext, tag):
decryptor = Cipher(
algorithms.AES(key),
modes.GCM(iv, tag, min_tag_length=12),
).decryptor()
decryptor.authenticate_additional_data(associated_data)
return decryptor.update(ciphertext) + decryptor.finalize()

View File

@@ -0,0 +1,174 @@
def b(value, length=1):
return value.to_bytes(length=length, byteorder='little')
def bi(value):
return int.from_bytes(value, byteorder='little')
class Player:
def __init__(self) -> None:
self.player_id = 0
self.player_name = b'\x45\x6d\x70\x74\x79\x50\x6c\x61\x79\x65\x72\x00\x00\x00\x00\x00'
self.token = 0
self.character_id = 0xff
self.last_character_id = 0xff
self.is_uncapped = 0
self.difficulty = 0xff
self.last_difficulty = 0xff
self.score = 0
self.last_score = 0
self.timer = 0
self.last_timer = 0
self.cleartype = 0
self.last_cleartype = 0
self.best_score_flag = 0
self.best_player_flag = 0
self.finish_flag = 0
self.player_state = 1
self.download_percent = 0
self.online = 0
self.last_timestamp = 0
self.extra_command_queue = []
self.song_unlock = b'\x00' * 512
self.start_command_num = 0
def set_player_name(self, player_name: str):
self.player_name = player_name.encode('ascii')
if len(self.player_name) > 16:
self.player_name = self.player_name[:16]
else:
self.player_name += b'\x00' * (16 - len(self.player_name))
class Room:
def __init__(self) -> None:
self.room_id = 0
self.room_code = 'AAAA00'
self.countdown = 0xffffffff
self.timestamp = 0
self.state = 0
self.song_idx = 0xffff
self.last_song_idx = 0xffff
self.song_unlock = b'\x00' * 512
self.host_id = 0
self.players = [Player(), Player(), Player(), Player()]
self.player_num = 0
self.interval = 1000
self.times = 100
self.round_switch = 0
self.command_queue = []
self.command_queue_length = 0
def get_players_info(self):
# 获取所有玩家信息
re = b''
for i in self.players:
re += b(i.player_id, 8) + b(i.character_id) + b(i.is_uncapped) + b(i.difficulty) + b(i.score, 4) + \
b(i.timer, 4) + b(i.cleartype) + b(i.player_state) + \
b(i.download_percent) + b(i.online) + b'\x00' + i.player_name
return re
def get_player_last_score(self):
# 获取上次曲目玩家分数返回bytes
if self.last_song_idx == 0xffff:
return b'\xff\xff\x00\x00\x00\x00\x00\x00\x00' * 4
re = b''
for i in range(4):
player = self.players[i]
if player.player_id != 0:
re += b(player.last_character_id) + b(player.last_difficulty) + b(player.last_score, 4) + b(
player.last_cleartype) + b(player.best_score_flag) + b(player.best_player_flag)
else:
re += b'\xff\xff\x00\x00\x00\x00\x00\x00\x00'
return re
def make_round(self):
# 轮换房主
for i in range(4):
if self.players[i].player_id == self.host_id:
for j in range(1, 4):
if self.players[(i + j) % 4].player_id != 0:
self.host_id = self.players[(i + j) % 4].player_id
break
break
def delete_player(self, player_index: int):
# 删除某个玩家
self.player_num -= 1
if self.players[player_index].player_id == self.host_id:
self.make_round()
self.players[player_index].online = 0
self.players[player_index] = Player()
self.update_song_unlock()
def update_song_unlock(self):
# 更新房间可用歌曲
r = bi(b'\xff' * 512)
for i in self.players:
if i.player_id != 0:
r &= bi(i.song_unlock)
self.song_unlock = b(r, 512)
def is_ready(self, old_state: int, player_state: int):
# 是否全部准备就绪
if self.state == old_state:
for i in self.players:
if i.player_id != 0 and (i.player_state != player_state or i.online == 0):
return False
return True
else:
return False
def is_finish(self):
# 是否全部进入结算
for i in self.players:
if i.player_id != 0 and (i.finish_flag == 0 or i.online == 0):
return False
return True
def make_finish(self):
# 结算
self.state = 8
self.last_song_idx = self.song_idx
max_score = 0
max_score_i = []
for i in range(4):
player = self.players[i]
if player.player_id != 0:
player.finish_flag = 0
player.last_timer = player.timer
player.last_score = player.score
player.last_cleartype = player.cleartype
player.last_character_id = player.character_id
player.last_difficulty = player.difficulty
player.best_player_flag = 0
if player.last_score > max_score:
max_score = player.last_score
max_score_i = [i]
elif player.last_score == max_score:
max_score_i.append(i)
for i in max_score_i:
self.players[i].best_player_flag = 1

View File

@@ -0,0 +1,8 @@
class Config:
TIME_LIMIT = 3600000
COMMAND_INTERVAL = 1000000
COUNTDOWM_TIME = 3999
PLAYER_TIMEOUT = 30000000

View File

@@ -0,0 +1,223 @@
import socketserver
import threading
import os
import random
import time
# import binascii
from . import aes
from .udp_parser import CommandParser
from .udp_class import Room, Player, bi
from .udp_config import Config
# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id}
link_play_data = {}
room_id_dict = {} # 'room_id': Room
room_code_dict = {} # 'room_code': Room
player_dict = {} # 'player_id' : Player
clean_timer = 0
def random_room_code():
# 随机生成房间号
re = ''
for _ in range(4):
re += chr(random.randint(65, 90))
for _ in range(2):
re += str(random.randint(0, 9))
return re
def clear_player(token):
# 清除玩家信息和token
del player_dict[link_play_data[token]['player_id']]
del link_play_data[token]
def clear_room(room):
# 清除房间信息
room_id = room.room_id
room_code = room.room_code
del room_id_dict[room_id]
del room_code_dict[room_code]
del room
def memory_clean(now):
# 内存清理
clean_room_list = []
clean_player_list = []
for token in link_play_data:
room = link_play_data[token]['room']
if now - room.timestamp >= Config.TIME_LIMIT:
clean_room_list.append(room.room_id)
if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
clean_player_list.append(token)
for room_id in room_id_dict:
if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT:
clean_room_list.append(room_id)
for room_id in clean_room_list:
if room_id in room_id_dict:
clear_room(room_id_dict[room_id])
for token in clean_player_list:
clear_player(token)
class UDPhandler(socketserver.BaseRequestHandler):
def handle(self):
client_msg, server = self.request
token = client_msg[:8]
iv = client_msg[8:20]
tag = client_msg[20:32]
ciphertext = client_msg[32:]
if int.from_bytes(token, byteorder='little') in link_play_data:
user = link_play_data[bi(token)]
else:
return None
plaintext = aes.decrypt(user['key'], b'', iv, ciphertext, tag)
# print(binascii.b2a_hex(plaintext))
commands = CommandParser(
user['room'], user['player_index']).get_commands(plaintext)
if user['room'].players[user['player_index']].player_id == 0:
clear_player(bi(token))
temp = []
for i in commands:
if i[:3] == b'\x06\x16\x12':
temp.append(i)
commands = temp
# 处理不能正确被踢的问题
for i in commands:
iv, ciphertext, tag = aes.encrypt(user['key'], i, b'')
# print(binascii.b2a_hex(i))
server.sendto(token + iv + tag[:12] +
ciphertext, self.client_address)
def server_run(ip, port):
server = socketserver.ThreadingUDPServer((ip, port), UDPhandler)
server.serve_forever()
def data_swap(conn):
clean_timer = 0
while True:
try:
data = conn.recv()
except EOFError:
break
now = round(time.time() * 1000)
if now - clean_timer >= Config.TIME_LIMIT:
clean_timer = now
memory_clean(now)
if data[0] == 1:
# 开房
key = os.urandom(16)
room_id = bi(os.urandom(8))
while room_id in room_id_dict and room_id == 0:
room_id = bi(os.urandom(8))
room = Room()
room.room_id = room_id
room_id_dict[room_id] = room
player_id = bi(os.urandom(3))
while player_id in player_dict and player_id == 0:
player_id = bi(os.urandom(3))
player = Player()
player.player_id = player_id
player.set_player_name(data[1])
player_dict[player_id] = player
player.song_unlock = data[2]
room.song_unlock = data[2]
room.host_id = player_id
room.players[0] = player
room.player_num = 1
room_code = random_room_code()
while room_code in room_code_dict:
room_code = random_room_code()
room.room_code = room_code
room_code_dict[room_code] = room
token = room_id
player.token = token
link_play_data[token] = {'key': key,
'room': room,
'player_index': 0,
'player_id': player_id}
conn.send((0, room_code, room_id, token, key, player_id))
elif data[0] == 2:
room_code = data[3].upper()
if room_code not in room_code_dict:
# 房间号错误
conn.send((1202, ))
else:
room = room_code_dict[room_code]
if room.player_num == 4:
# 满人
conn.send((1201, ))
elif room.state != 2:
# 无法加入
conn.send((1205, ))
else:
key = os.urandom(16)
token = bi(os.urandom(8))
player_id = bi(os.urandom(3))
while player_id in player_dict and player_id == 0:
player_id = bi(os.urandom(3))
player = Player()
player.player_id = player_id
player.set_player_name(data[1])
player.token = token
player_dict[player_id] = player
player.song_unlock = data[2]
room.update_song_unlock()
for i in range(4):
if room.players[i].player_id == 0:
room.players[i] = player
player_index = i
break
link_play_data[token] = {'key': key,
'room': room,
'player_index': player_index,
'player_id': player_id}
conn.send((0, room_code, room.room_id,
token, key, player_id, room.song_unlock))
elif data[0] == 3:
token = data[1]
if token in link_play_data:
r = link_play_data[token]
conn.send((0, r['room'].room_code, r['room'].room_id, r['key'],
r['room'].players[r['player_index']].player_id, r['room'].song_unlock))
else:
conn.send((108, ))
def link_play(conn, ip: str, port: int):
try:
server = threading.Thread(target=server_run, args=(ip, port))
data_exchange = threading.Thread(target=data_swap, args=(conn,))
server.start()
data_exchange.start()
except:
pass

View File

@@ -0,0 +1,333 @@
from operator import irshift
from .udp_sender import CommandSender
from .udp_class import bi, Room
from .udp_config import Config
import time
class CommandParser:
def __init__(self, room: Room, player_index: int = 0) -> None:
self.room = room
self.player_index = player_index
def get_commands(self, command):
self.command = command
l = {b'\x06\x16\x01': self.command_01,
b'\x06\x16\x02': self.command_02,
b'\x06\x16\x03': self.command_03,
b'\x06\x16\x04': self.command_04,
b'\x06\x16\x05': self.command_05,
b'\x06\x16\x06': self.command_06,
b'\x06\x16\x07': self.command_07,
b'\x06\x16\x08': self.command_08,
b'\x06\x16\x09': self.command_09,
b'\x06\x16\x0a': self.command_0a,
b'\x06\x16\x0b': self.command_0b
}
r = l[command[:3]]()
re = []
flag_13 = False
for i in range(max(bi(self.command[12:16]), self.room.players[self.player_index].start_command_num), self.room.command_queue_length):
if self.room.command_queue[i][:3] == b'\x06\x16\x13':
if flag_13:
break
flag_13 = True
re.append(self.room.command_queue[i])
if self.room.players[self.player_index].extra_command_queue:
re += self.room.players[self.player_index].extra_command_queue
self.room.players[self.player_index].extra_command_queue = []
if r:
re += r
return re
def command_01(self):
# 给房主
player_id = bi(self.command[24:32])
for i in self.room.players:
if i.player_id == player_id and i.online == 1:
self.room.host_id = player_id
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_10())
return None
def command_02(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
song_idx = bi(self.command[24:26])
flag = 2
if self.room.state == 2:
flag = 0
self.room.state = 3
self.room.song_idx = song_idx
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_11())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return [x.command_0d(flag)]
def command_03(self):
# 尝试进入结算
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player = self.room.players[self.player_index]
player.score = bi(self.command[24:28])
player.cleartype = self.command[28]
player.difficulty = self.command[29]
player.best_score_flag = self.command[30]
player.finish_flag = 1
player.last_timestamp -= Config.COMMAND_INTERVAL
self.room.last_song_idx = self.room.song_idx
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if self.room.is_finish():
self.room.make_finish()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_04(self):
# 踢人
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player_id = bi(self.command[24:32])
flag = 2
if self.room.players[self.player_index].player_id == self.room.host_id and player_id != self.room.host_id:
for i in range(4):
if self.room.players[i].player_id == player_id:
flag = 1
self.room.delete_player(i)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
self.room.update_song_unlock()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
break
return [x.command_0d(flag)]
def command_05(self):
pass
def command_06(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.state = 1
self.room.song_idx = 0xffff
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_07(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.players[self.player_index].song_unlock = self.command[24:536]
self.room.update_song_unlock()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
return None
def command_08(self):
self.room.round_switch = bi(self.command[24:25])
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_09(self):
re = []
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player = self.room.players[self.player_index]
if bi(self.command[12:16]) == 0:
player.online = 1
self.room.state = 1
self.room.update_song_unlock()
player.start_command_num = self.room.command_queue_length
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_15())
else:
if x.timestamp - player.last_timestamp >= Config.COMMAND_INTERVAL:
re.append(x.command_0c())
player.last_timestamp = x.timestamp
# 离线判断
for i in range(4):
if i != self.player_index:
t = self.room.players[i]
if t.player_id != 0:
if t.last_timestamp != 0:
if t.online == 1 and x.timestamp - t.last_timestamp >= 5000000:
t.online = 0
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
elif t.online == 0 and x.timestamp - t.last_timestamp >= Config.PLAYER_TIMEOUT:
self.room.delete_player(i)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
flag_11 = False
flag_12 = False
flag_13 = False
if player.online == 0:
flag_12 = True
player.online = 1
if self.room.is_ready(1, 1):
flag_13 = True
self.room.state = 2
if player.player_state != self.command[32]:
flag_12 = True
player.player_state = self.command[32]
if player.difficulty != self.command[33] and player.player_state != 5 and player.player_state != 6 and player.player_state != 7 and player.player_state != 8:
flag_12 = True
player.difficulty = self.command[33]
if player.cleartype != self.command[34] and player.player_state != 7 and player.player_state != 8:
flag_12 = True
player.cleartype = self.command[34]
if player.download_percent != self.command[35]:
flag_12 = True
player.download_percent = self.command[35]
if player.character_id != self.command[36]:
flag_12 = True
player.character_id = self.command[36]
if player.is_uncapped != self.command[37]:
flag_12 = True
player.is_uncapped = self.command[37]
if self.room.state == 3 and player.score != bi(self.command[24:28]):
flag_12 = True
player.score = bi(self.command[24:28])
if self.room.is_ready(3, 4):
flag_13 = True
self.room.countdown = Config.COUNTDOWM_TIME
self.room.timestamp = round(time.time() * 1000)
self.room.state = 4
if self.room.state == 4 or self.room.state == 5 or self.room.state == 6:
timestamp = round(time.time() * 1000)
self.room.countdown -= timestamp - self.room.timestamp
self.room.timestamp = timestamp
if self.room.state == 4 and self.room.countdown <= 0:
# 此处不清楚
self.room.state = 5
self.room.countdown = 5999
flag_11 = True
flag_13 = True
if self.room.state == 5 and self.room.is_ready(5, 6):
self.room.state = 6
flag_13 = True
if self.room.state == 5 and self.room.is_ready(5, 7):
self.room.state = 7
self.room.countdown = 0xffffffff
flag_13 = True
if self.room.state == 5 and self.room.countdown <= 0:
print('我怎么知道这是啥')
if self.room.state == 6 and self.room.countdown <= 0:
# 此处不清楚
self.room.state = 7
self.room.countdown = 0xffffffff
flag_13 = True
if self.room.countdown <= 0:
self.room.countdown = 0
if self.room.state == 7 or self.room.state == 8:
if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0:
player.last_timer = player.timer
player.last_score = player.score
player.timer = bi(self.command[28:32])
player.score = bi(self.command[24:28])
if player.timer != 0 or self.room.state != 8:
for i in self.room.players:
i.extra_command_queue.append(
x.command_0e(self.player_index))
if self.room.is_ready(8, 1):
flag_13 = True
self.room.state = 1
self.room.song_idx = 0xffff
if self.room.round_switch == 1:
self.room.make_round()
for i in self.room.players:
i.timer = 0
i.score = 0
if self.room.is_finish():
# 有人退房导致的结算
self.room.make_finish()
flag_13 = True
if flag_11:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_11())
if flag_12:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if flag_13:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return re
def command_0a(self):
# 退出房间
self.room.delete_player(self.player_index)
x = CommandSender(self.room)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if self.room.state == 3:
self.room.state = 1
self.room.song_idx = 0xffff
# self.room.command_queue_length += 1
# self.room.command_queue.append(x.command_11())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
return None
def command_0b(self):
# 推荐歌曲
song_idx = bi(self.command[16:18])
x = CommandSender(self.room)
for i in range(4):
if self.player_index != i and self.room.players[i].online == 1:
self.room.players[i].extra_command_queue.append(
x.command_0f(self.player_index, song_idx))
return None

View File

@@ -0,0 +1,46 @@
import time
from .udp_class import Room, b
class CommandSender:
def __init__(self, room: Room = Room()) -> None:
self.room = room
self.timestamp = round(time.time() * 1000000)
self.random_code = b'\x11\x11\x11\x11\x00\x00\x00\x00'
def command_0c(self):
return b'\x06\x16\x0c\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
def command_0d(self, code: int):
return b'\x06\x16\x0d\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(code) + b'\x07\x07\x07\x07\x07\x07\x07'
def command_0e(self, player_index: int):
# 分数广播
player = self.room.players[player_index]
return b'\x06\x16\x0e\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b'\x01' + b(player.last_score, 4) + b(player.last_timer, 4) + b(player.online)
def command_0f(self, player_index: int, song_idx: int):
# 歌曲推荐
player = self.room.players[player_index]
return b'\x06\x16\x0f\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(song_idx, 2) + b'\x06\x06\x06\x06\x06\x06'
def command_10(self):
# 房主宣告
return b'\x06\x16\x10\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8)
def command_11(self):
return b'\x06\x16\x11\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.get_players_info() + b'\x08\x08\x08\x08\x08\x08\x08\x08'
def command_12(self, player_index: int):
player = self.room.players[player_index]
return b'\x06\x16\x12\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(player_index) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b(player.online)
def command_13(self):
return b'\x06\x16\x13\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x01'
def command_14(self):
return b'\x06\x16\x14\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.song_unlock + b'\x08\x08\x08\x08\x08\x08\x08\x08'
def command_15(self):
return b'\x06\x16\x15\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.room.get_players_info() + self.room.song_unlock + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x09\x09\x09\x09\x09\x09\x09\x09\x09'

View File

1438
latest version/web/index.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,60 @@
#import sqlite3
from flask import (Blueprint, flash, g, redirect,
render_template, request, session, url_for)
import functools
bp = Blueprint('login', __name__, url_prefix='/web')
@bp.route('/login', methods=('GET', 'POST'))
def login():
# 登录
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
error = None
if username != 'admin' and password != 'admin':
error = '错误的用户名或密码 Incorrect username or password.'
if error is None:
session.clear()
session['user_id'] = 'admin'
return redirect(url_for('index.index'))
flash(error)
return render_template('web/login.html')
@bp.route('/logout')
def logout():
# 登出
session.clear()
flash('成功登出 Successfully log out.')
return redirect(url_for('index.index'))
def login_required(view):
# 登录验证,写成了修饰器
@functools.wraps(view)
def wrapped_view(**kwargs):
x = session.get('user_id')
# 少用户存在验证
if x is None:
return redirect(url_for('login.login'))
g.user = {'user_id': x, 'username': 'admin'}
return view(**kwargs)
return wrapped_view
#import sqlite3
from flask import (Blueprint, flash, g, redirect,
render_template, request, session, url_for)
import functools
from setting import Config
import hashlib
bp = Blueprint('login', __name__, url_prefix='/web')
@bp.route('/login', methods=('GET', 'POST'))
def login():
# 登录
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
error = None
if username != Config.USERNAME or password != Config.PASSWORD:
error = '错误的用户名或密码 Incorrect username or password.'
if error is None:
session.clear()
hash_session = username + \
hashlib.sha256(password.encode("utf8")).hexdigest()
hash_session = hashlib.sha256(
hash_session.encode("utf8")).hexdigest()
session['user_id'] = hash_session
return redirect(url_for('index.index'))
flash(error)
return render_template('web/login.html')
@bp.route('/logout')
def logout():
# 登出
session.clear()
flash('成功登出 Successfully log out.')
return redirect(url_for('index.index'))
def login_required(view):
# 登录验证,写成了修饰器
@functools.wraps(view)
def wrapped_view(**kwargs):
x = session.get('user_id')
hash_session = Config.USERNAME + \
hashlib.sha256(Config.PASSWORD.encode("utf8")).hexdigest()
hash_session = hashlib.sha256(hash_session.encode("utf8")).hexdigest()
if x != hash_session:
return redirect(url_for('login.login'))
g.user = {'user_id': x, 'username': Config.USERNAME}
return view(**kwargs)
return wrapped_view

View File

@@ -0,0 +1,544 @@
import os
from server.sql import Connect
import time
import json
import server.arcscore
import hashlib
from random import Random
from setting import Config
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def random_str(randomlength=10):
# 随机生成字符串
s = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
s += chars[random.randint(0, length)]
return s
def get_table_info(c, table_name):
# 得到表结构,返回主键列表和字段名列表
pk = []
name = []
c.execute('''pragma table_info ("'''+table_name+'''")''')
x = c.fetchall()
if x:
for i in x:
name.append(i[1])
if i[5] != 0:
pk.append(i[1])
return pk, name
def get_sql_select_table(table_name, get_field, where_field=[], where_value=[]):
# sql语句拼接select ... from ... where ...
sql = 'select '
sql_dict = {}
if len(get_field) >= 2:
sql += get_field[0]
for i in range(1, len(get_field)):
sql += ',' + get_field[i]
sql += ' from ' + table_name
elif len(get_field) == 1:
sql += get_field[0] + ' from ' + table_name
else:
sql += '* from ' + table_name
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 1:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
sql += ' order by rowid'
return sql, sql_dict
def get_sql_insert_table(table_name, field, value):
# sql语句拼接insert into ...(...) values(...)
sql = 'insert into ' + table_name + '('
sql_dict = {}
sql2 = ''
if len(field) >= 2:
sql += field[0]
sql2 += ':' + field[0]
sql_dict[field[0]] = value[0]
for i in range(1, len(field)):
sql += ',' + field[i]
sql2 += ', :' + field[i]
sql_dict[field[i]] = value[i]
sql += ') values('
elif len(field) == 1:
sql += field[0] + ') values('
sql2 += ':' + field[0]
sql_dict[field[0]] = value[0]
else:
return 'error', {}
sql += sql2 + ')'
return sql, sql_dict
def get_sql_delete_table(table_name, where_field=[], where_value=[]):
# sql语句拼接delete from ... where ...
sql = 'delete from ' + table_name
sql_dict = {}
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 1:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
return sql, sql_dict
def update_one_table(c1, c2, table_name):
# 从c1向c2更新数据表c1中存在的信息不变
c1.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
c2.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
if not c1.fetchone() or not c2.fetchone():
return 'error'
db1_pk, db1_name = get_table_info(c1, table_name)
db2_pk, db2_name = get_table_info(c2, table_name)
if db1_pk != db2_pk:
return 'error'
field = []
for i in db1_name:
if i in db2_name:
field.append(i)
sql, sql_dict = get_sql_select_table(table_name, db1_pk)
c1.execute(sql)
x = c1.fetchall()
sql, sql_dict = get_sql_select_table(table_name, field)
c1.execute(sql)
y = c1.fetchall()
if x:
for i in range(0, len(x)):
sql, sql_dict = get_sql_select_table(
table_name, [], db1_pk, list(x[i]))
sql = 'select exists(' + sql + ')'
c2.execute(sql, sql_dict)
if c2.fetchone() == (1,): # 如果c2里存在先删除
sql, sql_dict = get_sql_delete_table(
table_name, db1_pk, list(x[i]))
c2.execute(sql, sql_dict)
sql, sql_dict = get_sql_insert_table(
table_name, field, list(y[i]))
c2.execute(sql, sql_dict)
return None
def update_user_char(c):
# 用character数据更新user_char
c.execute('''select character_id, max_level, is_uncapped from character''')
x = c.fetchall()
c.execute('''select user_id from user''')
y = c.fetchall()
if x and y:
for j in y:
for i in x:
c.execute('''delete from user_char_full where user_id=:a and character_id=:b''', {
'a': j[0], 'b': i[0]})
exp = 25000 if i[1] == 30 else 10000
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(j[0], i[0], i[1], exp, i[2], 0))
def update_database():
# 将old数据库不存在数据加入到新数据库上并删除old数据库
# 对于arcaea_datebase.db更新一些表并用character数据更新user_char_full
# 对于arcsong.db更新songs
if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"):
with Connect('./database/old_arcaea_database.db') as c1:
with Connect() as c2:
update_one_table(c1, c2, 'user')
update_one_table(c1, c2, 'friend')
update_one_table(c1, c2, 'best_score')
update_one_table(c1, c2, 'recent30')
update_one_table(c1, c2, 'user_world')
update_one_table(c1, c2, 'item')
update_one_table(c1, c2, 'user_item')
update_one_table(c1, c2, 'purchase')
update_one_table(c1, c2, 'purchase_item')
update_one_table(c1, c2, 'user_save')
update_one_table(c1, c2, 'login')
update_one_table(c1, c2, 'present')
update_one_table(c1, c2, 'user_present')
update_one_table(c1, c2, 'present_item')
update_one_table(c1, c2, 'redeem')
update_one_table(c1, c2, 'user_redeem')
update_one_table(c1, c2, 'redeem_item')
update_one_table(c1, c2, 'role')
update_one_table(c1, c2, 'user_role')
update_one_table(c1, c2, 'power')
update_one_table(c1, c2, 'role_power')
update_one_table(c1, c2, 'api_login')
update_one_table(c1, c2, 'user_char')
if not Config.UPDATE_WITH_NEW_CHARACTER_DATA:
update_one_table(c1, c2, 'character')
update_user_char(c2) # 更新user_char_full
os.remove('database/old_arcaea_database.db')
# songs
if os.path.isfile("database/old_arcsong.db") and os.path.isfile("database/arcsong.db"):
with Connect('./database/old_arcsong.db') as c1:
with Connect('./database/arcsong.db') as c2:
update_one_table(c1, c2, 'songs')
os.remove('database/old_arcsong.db')
def unlock_all_user_item(c):
# 解锁所有用户购买
c.execute('''select user_id from user''')
x = c.fetchall()
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
if x and y:
for i in x:
for j in y:
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
return
def unlock_user_item(c, user_id):
# 解锁用户购买
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
for j in y:
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
return
def get_all_item():
# 所有物品数据查询
with Connect() as c:
c.execute('''select * from item''')
x = c.fetchall()
re = []
if x:
for i in x:
re.append({'item_id': i[0],
'type': i[1],
'is_available': int2b(i[2])
})
return re
def get_all_purchase():
# 所有购买数据查询
with Connect() as c:
c.execute('''select * from purchase''')
x = c.fetchall()
re = []
if x:
for i in x:
discount_from = None
discount_to = None
if i[3] and i[3] >= 0:
discount_from = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(int(i[3])/1000))
if i[4] and i[4] >= 0:
discount_to = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(int(i[4])//1000))
c.execute(
'''select * from purchase_item where purchase_name=?''', (i[0],))
y = c.fetchall()
items = []
if y:
for j in y:
items.append({'item_id': j[1], 'type': j[2], 'amount':j[3]})
re.append({'purchase_name': i[0],
'price': i[1],
'orig_price': i[2],
'discount_from': discount_from,
'discount_to': discount_to,
'items': items
})
return re
def update_one_save(c, user_id):
# 同步指定用户存档
# 注意best_score表不比较直接覆盖
c.execute('''select scores_data, clearlamps_data from user_save where user_id=:a''', {
'a': user_id})
x = c.fetchone()
if x:
scores = json.loads(x[0])[""]
clearlamps = json.loads(x[1])[""]
clear_song_id_difficulty = []
clear_state = []
for i in clearlamps:
clear_song_id_difficulty.append(i['song_id']+str(i['difficulty']))
clear_state.append(i['clear_type'])
for i in scores:
rating = server.arcscore.get_one_ptt(
i['song_id'], i['difficulty'], i['score'])
if rating < 0:
rating = 0
try:
index = clear_song_id_difficulty.index(
i['song_id'] + str(i['difficulty']))
except:
index = -1
if index != -1:
clear_type = clear_state[index]
else:
clear_type = 0
c.execute('''delete from best_score where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': i['song_id'], 'c': i['difficulty']})
c.execute('''insert into best_score values(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n)''', {
'a': user_id, 'b': i['song_id'], 'c': i['difficulty'], 'd': i['score'], 'e': i['shiny_perfect_count'], 'f': i['perfect_count'], 'g': i['near_count'], 'h': i['miss_count'], 'i': i['health'], 'j': i['modifier'], 'k': i['time_played'], 'l': clear_type, 'm': clear_type, 'n': rating})
ptt = server.arcscore.get_user_ptt(c, user_id) # 更新PTT
c.execute('''update user set rating_ptt=:a where user_id=:b''', {
'a': ptt, 'b': user_id})
return
def update_all_save(c):
# 同步所有用户存档
c.execute('''select user_id from user_save''')
x = c.fetchall()
if x:
for i in x:
update_one_save(c, i[0])
return
def add_one_present(present_id, expire_ts, description, item_id, item_type, item_amount):
# 添加一个奖励
message = None
with Connect() as c:
c.execute(
'''select exists(select * from present where present_id=:a)''', {'a': present_id})
if c.fetchone() == (0,):
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (1,):
c.execute('''insert into present values(:a,:b,:c)''', {
'a': present_id, 'b': expire_ts, 'c': description})
c.execute('''insert into present_item values(?,?,?,?)''',
(present_id, item_id, item_type, item_amount))
message = '添加成功 Successfully add it.'
else:
message = '物品不存在 The item does not exist.'
else:
message = '奖励已存在 The present exists.'
return message
def delete_one_present(present_id):
# 删除一个奖励
message = None
with Connect() as c:
c.execute(
'''select exists(select * from present where present_id=:a)''', {'a': present_id})
if c.fetchone() == (1,):
c.execute('''delete from present where present_id = :a''',
{'a': present_id})
c.execute('''delete from user_present where present_id =:a''', {
'a': present_id})
c.execute('''delete from present_item where present_id =:a''', {
'a': present_id})
message = '删除成功 Successfully delete it.'
else:
message = '奖励不存在 The present does not exist.'
return message
def is_present_available(c, present_id):
# 判断present_id是否有效
c.execute(
'''select exists(select * from present where present_id = :a)''', {'a': present_id})
if c.fetchone() == (1,):
return True
else:
return False
def deliver_one_user_present(c, present_id, user_id):
# 为指定玩家添加奖励,重复添加不会提示
c.execute('''select exists(select * from user_present where user_id=:a and present_id=:b)''',
{'a': user_id, 'b': present_id})
if c.fetchone() == (0,):
c.execute('''insert into user_present values(:a,:b)''',
{'a': user_id, 'b': present_id})
return
def deliver_all_user_present(c, present_id):
# 为所有玩家添加奖励
c.execute('''select user_id from user''')
x = c.fetchall()
if x:
c.execute('''delete from user_present where present_id=:b''',
{'b': present_id})
for i in x:
c.execute('''insert into user_present values(:a,:b)''',
{'a': i[0], 'b': present_id})
return
def add_one_redeem(code, redeem_type, item_id, item_type, item_amount):
# 添加一个兑换码
message = None
with Connect() as c:
c.execute(
'''select exists(select * from redeem where code=:a)''', {'a': code})
if c.fetchone() == (0,):
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (1,):
c.execute('''insert into redeem values(:a,:c)''', {
'a': code, 'c': redeem_type})
c.execute('''insert into redeem_item values(?,?,?,?)''',
(code, item_id, item_type, item_amount))
message = '添加成功 Successfully add it.'
else:
message = '物品不存在 The item does not exist.'
else:
message = '兑换码已存在 The redeem code exists.'
return message
def add_some_random_redeem(amount, redeem_type, item_id, item_type, item_amount):
# 随机生成一堆10位的兑换码
message = None
with Connect() as c:
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (0,):
return '物品不存在 The item does not exist.'
i = 1
while i <= amount:
code = random_str()
c.execute(
'''select exists(select * from redeem where code=:a)''', {'a': code})
if c.fetchone() == (0,):
c.execute('''insert into redeem values(:a,:c)''',
{'a': code, 'c': redeem_type})
c.execute('''insert into redeem_item values(?,?,?,?)''',
(code, item_id, item_type, item_amount))
i += 1
message = '添加成功 Successfully add it.'
return message
def delete_one_redeem(code):
# 删除一个兑换码
message = None
with Connect() as c:
c.execute(
'''select exists(select * from redeem where code=:a)''', {'a': code})
if c.fetchone() == (1,):
c.execute('''delete from redeem where code = :a''', {'a': code})
c.execute(
'''delete from user_redeem where code =:a''', {'a': code})
c.execute(
'''delete from redeem_item where code =:a''', {'a': code})
message = '删除成功 Successfully delete it.'
else:
message = '兑换码不存在 The redeem code does not exist.'
return message
def change_userpwd(c, user_id, password):
# 修改用户密码
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute('''update user set password =:a where user_id=:b''',
{'a': hash_pwd, 'b': user_id})
return
def ban_one_user(c, user_id):
# 封禁用户
c.execute('''update user set password = '' where user_id=:a''',
{'a': user_id})
c.execute('''delete from login where user_id=:a''', {'a': user_id})
return
def clear_user_score(c, user_id):
# 清除用户所有成绩包括best_score和recent30以及recent数据但不包括云端存档
c.execute('''update user set rating_ptt=0, song_id='', difficulty=0, score=0, shiny_perfect_count=0, perfect_count=0, near_count=0, miss_count=0, health=0, time_played=0, rating=0, world_rank_score=0 where user_id=:a''',
{'a': user_id})
c.execute('''delete from best_score where user_id=:a''', {'a': user_id})
c.execute('''delete from recent30 where user_id=:a''', {'a': user_id})
return

View File

@@ -1,104 +1,110 @@
import time
def get_user_score(c, user_id, limit=-1):
# 返回用户的所有歌曲数据,带排名,返回字典列表
if limit >= 0:
c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b''',
{'a': user_id, 'b': limit})
else:
c.execute(
'''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id})
x = c.fetchall()
r = []
if x:
rank = 0
for i in x:
rank += 1
r.append({
"song_id": i[1],
"difficulty": i[2],
"score": i[3],
"shiny_perfect_count": i[4],
"perfect_count": i[5],
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(i[10])),
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13],
"rank": rank
})
return r
def get_user(c, user_id):
# 得到user表部分用户信息返回字典
c.execute('''select * from user where user_id = :a''', {'a': user_id})
x = c.fetchone()
r = None
if x:
join_date = None
time_played = None
if x[3]:
join_date = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(x[3])//1000))
if x[20]:
time_played = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(x[20])//1000))
r = {'name': x[1],
'user_id': user_id,
'join_date': join_date,
'user_code': x[4],
'rating_ptt': x[5],
'song_id': x[11],
'difficulty': x[12],
'score': x[13],
'shiny_perfect_count': x[14],
'perfect_count': x[15],
'near_count': x[16],
'miss_count': x[17],
'time_played': time_played,
'clear_type': x[21],
'rating': x[22]
}
return r
def get_user_recent30(c, user_id):
# 获取玩家recent30信息并计算这一部分的ptt返回字典列表和一个值
c.execute('''select * from recent30 where user_id=:a''', {'a': user_id})
sumr = 0
x = c.fetchone()
r = []
if x is not None:
r30 = []
s30 = []
for i in range(1, 61, 2):
if x[i] is not None:
r30.append(float(x[i]))
s30.append(x[i+1])
else:
r30.append(0)
s30.append('')
r30, s30 = (list(t) for t in zip(*sorted(zip(r30, s30), reverse=True)))
songs = []
i = 0
while len(songs) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None:
if s30[i] not in songs:
sumr += r30[i]
songs.append(s30[i])
i += 1
for i in range(0, 30):
if s30[i]:
r.append({
'song_id': s30[i][:-1],
'difficulty': int(s30[i][-1]),
'rating': r30[i]
})
return r, sumr / 10
import time
def get_user_score(c, user_id, limit=-1, offset=0):
# 返回用户的所有歌曲数据,带排名,返回字典列表
if limit >= 0:
c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b offset :c''',
{'a': user_id, 'b': limit, 'c': offset})
else:
c.execute(
'''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id})
x = c.fetchall()
r = []
if x:
rank = 0
for i in x:
rank += 1
r.append({
"song_id": i[1],
"difficulty": i[2],
"score": i[3],
"shiny_perfect_count": i[4],
"perfect_count": i[5],
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(i[10])),
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13],
"rank": rank
})
return r
def get_user(c, user_id):
# 得到user表部分用户信息返回字典
c.execute('''select * from user where user_id = :a''', {'a': user_id})
x = c.fetchone()
r = None
if x:
join_date = None
time_played = None
if x[3]:
join_date = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(x[3])//1000))
if x[20]:
time_played = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(x[20])//1000))
if x[2] == '':
ban_flag = True
else:
ban_flag = False
r = {'name': x[1],
'user_id': user_id,
'join_date': join_date,
'user_code': x[4],
'rating_ptt': x[5],
'song_id': x[11],
'difficulty': x[12],
'score': x[13],
'shiny_perfect_count': x[14],
'perfect_count': x[15],
'near_count': x[16],
'miss_count': x[17],
'time_played': time_played,
'clear_type': x[21],
'rating': x[22],
'ticket': x[26],
'ban_flag': ban_flag
}
return r
def get_user_recent30(c, user_id):
# 获取玩家recent30信息并计算recent10的ptt返回字典列表和一个值
c.execute('''select * from recent30 where user_id=:a''', {'a': user_id})
sumr = 0
x = c.fetchone()
r = []
if x is not None:
r30 = []
s30 = []
for i in range(1, 61, 2):
if x[i] is not None:
r30.append(float(x[i]))
s30.append(x[i+1])
else:
r30.append(0)
s30.append('')
r30, s30 = (list(t) for t in zip(*sorted(zip(r30, s30), reverse=True)))
songs = []
i = 0
while len(songs) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None:
if s30[i] not in songs:
sumr += r30[i]
songs.append(s30[i])
i += 1
for i in range(0, 30):
if s30[i]:
r.append({
'song_id': s30[i][:-1],
'difficulty': int(s30[i][-1]),
'rating': r30[i]
})
return r, sumr / 10

File diff suppressed because it is too large Load Diff

View File

@@ -1,141 +0,0 @@
import sqlite3
import hashlib
import time
def arc_login(name: str, password: str) -> str: # 登录判断
# 查询数据库中的user表验证账号密码返回并记录token
# token采用user_id和时间戳连接后hash生成真的是瞎想的没用bear
# 密码和token的加密方式为 SHA-256
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute('''select user_id from user where name = :name and password = :password''', {
'name': name, 'password': hash_pwd})
x = c.fetchone()
if x is not None:
user_id = str(x[0])
now = int(time.time() * 1000)
token = hashlib.sha256((user_id + str(now)).encode("utf8")).hexdigest()
c.execute(
'''select exists(select * from login where user_id = :user_id)''', {"user_id": user_id})
if c.fetchone() == (1,): # 删掉多余token
c.execute('''delete from login where user_id = :user_id''',
{'user_id': user_id})
c.execute('''insert into login(access_token, user_id) values(:access_token, :user_id)''', {
'user_id': user_id, 'access_token': token})
conn.commit()
conn.close()
return token
conn.commit()
conn.close()
return None
def arc_register(name: str, password: str): # 注册
# 账号注册只记录hash密码和用户名生成user_id和user_code自动登录返回token
# token和密码的处理同登录部分
def build_user_code(c):
# 生成9位的user_code用的自然是随机
import random
flag = True
while flag:
user_code = ''.join([str(random.randint(0, 9)) for i in range(9)])
c.execute('''select exists(select * from user where user_code = :user_code)''',
{'user_code': user_code})
if c.fetchone() == (0,):
flag = False
return user_code
def build_user_id(c):
# 生成user_id往后加1
c.execute('''select max(user_id) from user''')
x = c.fetchone()
if x[0] is not None:
return x[0] + 1
else:
return 2000001
def insert_user_char(c, user_id):
# 为用户添加所有可用角色
for i in range(0, 38):
if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36]:
sql = 'insert into user_char values('+str(user_id)+','+str(
i)+''',30,25000,25000,90,90,90,'',0,0,'',0,1,1)'''
c.execute(sql)
else:
if i != 5:
sql = 'insert into user_char values('+str(user_id)+','+str(
i)+''',30,25000,25000,90,90,90,'',0,0,'',0,0,0)'''
c.execute(sql)
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if c.fetchone() == (0,):
user_code = build_user_code(c)
user_id = build_user_id(c)
now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled)
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0)
''', {'user_code': user_code, 'user_id': user_id, 'join_date': now, 'name': name, 'password': hash_pwd})
c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id})
token = hashlib.sha256(
(str(user_id) + str(now)).encode("utf8")).hexdigest()
c.execute('''insert into login(access_token, user_id) values(:access_token, :user_id)''', {
'user_id': user_id, 'access_token': token})
insert_user_char(c, user_id)
conn.commit()
conn.close()
return user_id, token, 0
else:
conn.commit()
conn.close()
return None, None, 101
def token_get_id(token: str):
# 用token获取id没有考虑不同用户token相同情况说不定会有bug
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
c.execute('''select user_id from login where access_token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
conn.commit()
conn.close()
return x[0]
else:
conn.commit()
conn.close()
return None
def code_get_id(user_code):
# 用user_code获取id
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
c.execute('''select user_id from user where user_code = :a''',
{'a': user_code})
x = c.fetchone()
if x is not None:
conn.commit()
conn.close()
return x[0]
else:
conn.commit()
conn.close()
return None

Binary file not shown.

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