mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-09 09:17:26 +08:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ab6c43cb | ||
|
|
15134173c9 | ||
|
|
9f41ef0a70 | ||
|
|
af3e91b3e8 | ||
|
|
9d746eab2d | ||
|
|
6fcca17918 | ||
|
|
9de62d3645 | ||
|
|
96fbd26342 | ||
|
|
aa77a4c903 | ||
|
|
6075bda5d8 | ||
|
|
409c2c032b | ||
|
|
e8c15f5069 | ||
|
|
35c34f25d5 | ||
|
|
4b8c5a520e | ||
|
|
1235733ddf | ||
|
|
cb6425a0d1 | ||
|
|
3a1c731e24 | ||
|
|
5de274fdf5 | ||
|
|
b60457c38b | ||
|
|
35b954e549 | ||
|
|
ceebba4664 | ||
|
|
5bc9b9a3dc | ||
|
|
d9a543bc5a | ||
|
|
4666c9c153 | ||
|
|
32bcbb0ccd | ||
|
|
34497d0638 | ||
|
|
23bf3c020f | ||
|
|
e1fc1bbcd1 | ||
|
|
355ec3557f | ||
|
|
cc4ac192e7 | ||
|
|
5068c7215c | ||
|
|
99f05cabb7 | ||
|
|
1a097bc4d0 | ||
|
|
849f4f7260 | ||
|
|
add81ee639 | ||
|
|
b927ad23f8 | ||
|
|
8bc74b36c7 | ||
|
|
7dc8bfea9a | ||
|
|
d0be49fe20 | ||
|
|
b494b93c14 | ||
|
|
a523ff5aae | ||
|
|
acbe9fc4a1 | ||
|
|
9422bf7182 | ||
|
|
53acad4ffb | ||
|
|
13048e57f4 | ||
|
|
39d3f073ba | ||
|
|
2cf6595478 | ||
|
|
7c3bc99570 | ||
|
|
168e5f0b12 | ||
|
|
9286caf900 | ||
|
|
b0647bedb6 | ||
|
|
f49a868707 | ||
|
|
28bca25092 | ||
|
|
0227d2b44a | ||
|
|
0d95b886bc | ||
|
|
9b61acd163 | ||
|
|
be134bbc6e | ||
|
|
c8d0463b9e | ||
|
|
871c2082a8 | ||
|
|
14b685850c | ||
|
|
3fbf473de2 | ||
|
|
8a3cec05b7 | ||
|
|
e3d81436d0 | ||
|
|
941a79ccc7 | ||
|
|
62902a561c | ||
|
|
1660bc5899 | ||
|
|
d577246d28 | ||
|
|
9fd626468e | ||
|
|
c51a1b4e85 | ||
|
|
0e42123417 | ||
|
|
e216e6144b | ||
|
|
b70c435a4a | ||
|
|
eb03d2be93 | ||
|
|
fcb5d264ef | ||
|
|
d617951e1f | ||
|
|
febf931f1a | ||
|
|
981961d6a1 | ||
|
|
a90380df4c | ||
|
|
4f66f492fc | ||
|
|
df181f104e | ||
|
|
a890a9af4a | ||
|
|
c6bb4c9afb | ||
|
|
98358ad216 | ||
|
|
e0e6128826 | ||
|
|
08872399cc | ||
|
|
29a6cfaac1 | ||
|
|
f25bd4130e | ||
|
|
68c757f9d7 | ||
|
|
26216bf84a | ||
|
|
5771d0c669 |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
*.log
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
84
README.md
84
README.md
@@ -2,41 +2,50 @@
|
|||||||
一个微型的Arcaea本地服务器 A small local server for Arcaea
|
一个微型的Arcaea本地服务器 A small local server for Arcaea
|
||||||
|
|
||||||
## 简介 Introduction
|
## 简介 Introduction
|
||||||
这是基于Python以及Flask的微型本地Arcaea服务器,可以模拟游戏的主要功能,一时兴起在五天之内糊了出来。这可能是我第一次写这种大程序,若有不妥之处,敬请谅解。
|
这是基于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.
|
|
||||||
|
本程序主要用于学习研究,不得用于任何商业行为,否则后果自负,这不是强制要求,只是一个提醒与警告。
|
||||||
|
|
||||||
|
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!
|
> It looks stupid, but it works!
|
||||||
|
|
||||||
## 特性 Features
|
## 特性 Features
|
||||||
有以下 We have:
|
有以下 We have:
|
||||||
- 登录、注册 Login and registration
|
- 登录、注册 Login and registration
|
||||||
|
- 多设备登录 Multi device login
|
||||||
- 成绩上传 Score upload
|
- 成绩上传 Score upload
|
||||||
- PTT
|
- PTT
|
||||||
|
- 世界排名 Global rank
|
||||||
- 排名 Rank
|
- 排名 Rank
|
||||||
|
- 段位系统 Course system
|
||||||
|
- Link Play
|
||||||
- 好友系统 Friends
|
- 好友系统 Friends
|
||||||
- 数据同步 Data synchronization
|
- 数据同步 Data synchronization
|
||||||
- 爬梯 Climbing steps
|
- 爬梯 Climbing steps
|
||||||
- 自定义世界模式 Customizable World Mode
|
- 自定义世界模式 Customizable World Mode
|
||||||
- 自定义歌曲下载 Customizable songs download
|
- 自定义歌曲下载 Customizable songs download
|
||||||
- 单曲和曲包购买(没啥用) Single songs and song packs purchase(useless)
|
- 单曲和曲包购买(没啥用) Single songs and song packs purchase(useless)
|
||||||
- 全角色立绘 All character drawings
|
- 奖励系统 Present system
|
||||||
- 角色技能 Character skills
|
- 兑换码系统 Redeem code system
|
||||||
- 自定义角色属性 Customizable characters attributes
|
- 角色系统 Character system
|
||||||
- 全剧情解锁 Unlock all the stories
|
- 全剧情解锁 Unlock all the stories
|
||||||
- 后台查询 Background search
|
- 后台查询 Background search
|
||||||
- 后台自定义信息 Customize some things in the background
|
- 后台自定义信息 Customize some things in the background
|
||||||
- 成绩校验 Score check
|
- 成绩校验 Score check
|
||||||
- 下载校验 Download check
|
- 下载校验 Download check
|
||||||
|
- 服务器日志 Server log
|
||||||
|
|
||||||
没有以下 We don't have:
|
没有以下 We don't have:
|
||||||
- 角色数值 Character characteristic value
|
|
||||||
- 数据同步的时间记录 The record of time for data synchronization
|
|
||||||
- 服务器安全性保证 Server security assurance
|
- 服务器安全性保证 Server security assurance
|
||||||
- 登录奖励 Login present
|
|
||||||
|
|
||||||
可能有问题 There may be problems:
|
可能有问题 There may be problems:
|
||||||
- Recent 30
|
- Recent 30
|
||||||
- 一些歌曲的解锁 Some Songs' unlocking
|
- 一些歌曲的解锁 Some songs' unlocking
|
||||||
|
- 同设备多共存登录 Multiple app logins on the same device
|
||||||
|
|
||||||
## 说明 Statement
|
## 说明 Statement
|
||||||
只是很有趣,用处探索中。
|
只是很有趣,用处探索中。
|
||||||
@@ -46,37 +55,57 @@ It is just so interesting. What it can do is under exploration.
|
|||||||
## 下载 Download
|
## 下载 Download
|
||||||
[这里 Here](https://github.com/Lost-MSth/Arcaea-server/releases)
|
[这里 Here](https://github.com/Lost-MSth/Arcaea-server/releases)
|
||||||
|
|
||||||
[Arcaea](https://konmai.cn/#arcaea)
|
[Arcaea-CN official](https://arcaea.lowiro.com/zh)
|
||||||
|
[Arcaea-lowi.ro](https://lowi.ro)
|
||||||
|
[Arcaea-RhyDown](https://rhydown.com)
|
||||||
|
|
||||||
## 更新日志 Update log
|
## 更新日志 Update log
|
||||||
只保留最新版本 Only keep the latest version.
|
只保留最新版本 Only keep the latest version.
|
||||||
|
|
||||||
> 提醒:更新时请注意保留原先的数据库,以防数据丢失
|
> 提醒:更新时请注意保留原先的数据库,以防数据丢失。
|
||||||
>
|
>
|
||||||
> Tips: When updating, please keep the original database in case of data loss.
|
> Tips: When updating, please keep the original database in case of data loss.
|
||||||
|
|
||||||
### Version 1.8.1
|
|
||||||
- 适用于Arcaea 3.3.1版本 For Arcaea 3.3.1
|
|
||||||
- 更新了歌曲数据库 Update the song database.
|
|
||||||
- 修复了一些Bug Fix some bugs.
|
|
||||||
|
|
||||||
> 提醒:只是一次小更新,本次更新前后登录信息无法同步,需重新登录(我忘记给login表加主键了同步不了哈哈哈)
|
### Version 2.9
|
||||||
> 已发现的问题:设置Favorite角色可能并不有效
|
- 适用于Arcaea 4.0.0版本 For Arcaea 4.0.0
|
||||||
> 预告:登录奖励系统
|
- 新增段位/课题模式 Add Dan/Course Mode.
|
||||||
|
- 新搭档 **光(Fatalis)** 已解锁 Unlock the character **Hikari(Fatalis)**.
|
||||||
|
- 新增对`光(Fatalis)`的技能的支持 Add support for the skill of `Hikari(Fatalis)`.
|
||||||
|
- 新增对额外文件下载的支持 Add support for downloading additional files.
|
||||||
|
- 新增对`结局挑战`的云端存档的支持 Add support for the cloud save of `finale challenge`.
|
||||||
|
- 修复`Link Play`模式下当房主退出时,新房主没有立即显示的问题 Fix a bug that the other player will not become the host of the room at once, when the player disconnect in `Link Play`.
|
||||||
|
- 修改了`Link Play`模式的一些时间常数 Change some constants of time in `Link Play` mode.
|
||||||
|
- 重构一些代码,可能引入了新Bug Refactoring some codes and there may be more bugs.
|
||||||
|
|
||||||
|
- 以下是累积更新 The following are cumulative updates:
|
||||||
|
- 修复一个`v2.8.5`版本引入的有关无法加好友的问题 Fix a bug of unable to add friend which arose in the `version 2.8.5`.
|
||||||
|
- 修复`Link Play`模式下重复计算歌曲完成状态的问题 Fix a bug of duplicate calculating finishing states when players finish playing the chart in `Link Play`.
|
||||||
|
|
||||||
|
> 注意:
|
||||||
|
> - 不再提供歌曲数据
|
||||||
|
> - 目前尽量不会对前端web页面增补功能
|
||||||
|
> - `对立`不会死
|
||||||
>
|
>
|
||||||
> Tips: It is only a small update. The login information before and after this update can't be synchronized, so you need to log in again. (I forgot to add a primary key to the login table, so it can't be synchronized. lol)
|
> Note:
|
||||||
> Problem found: Setting the favorite character may be useless.
|
> - No longer provide song data.
|
||||||
> Preview: Login present system
|
> - Since now, probably not add functions to the front-end web pages.
|
||||||
|
> - `Tairitsu` will not die.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 运行环境与依赖 Running environment and requirements
|
## 运行环境与依赖 Running environment and requirements
|
||||||
- Windows操作系统 Windows operating system
|
- Windows/Linux/Mac OS/Android...
|
||||||
- Python 3
|
- Python 3
|
||||||
- Flask模块 Flask module
|
- Flask module >= 2.0, Cryptography module
|
||||||
- Charles
|
- Charles, IDA, proxy app... (optional)
|
||||||
|
|
||||||
|
<!--
|
||||||
## 环境搭建 Environment construction
|
## 环境搭建 Environment construction
|
||||||
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA)
|
[中文](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)
|
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Environment-construction)
|
||||||
|
-->
|
||||||
|
|
||||||
## 使用说明 Instruction for use
|
## 使用说明 Instruction for use
|
||||||
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
|
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
|
||||||
@@ -86,9 +115,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)
|
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E6%B3%A8%E6%84%8F)
|
||||||
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Attentions)
|
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Attentions)
|
||||||
|
|
||||||
|
|
||||||
## 鸣谢 Thanks
|
## 鸣谢 Thanks
|
||||||
歌曲数据库来自 Using song database from
|
~~歌曲数据库来自 Using song database from
|
||||||
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)
|
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)~~
|
||||||
|
> 从v2.9开始不再提供歌曲数据
|
||||||
|
> Since v2.9, song data will not be provided.
|
||||||
|
|
||||||
网站图标来自 Using favicon from [black fate - てんてん - pixiv](https://www.pixiv.net/artworks/82374369)
|
网站图标来自 Using favicon from [black fate - てんてん - pixiv](https://www.pixiv.net/artworks/82374369)
|
||||||
|
|
||||||
|
|||||||
10
latest version/api/__init__.py
Normal file
10
latest version/api/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from . import users
|
||||||
|
from . import songs
|
||||||
|
from . import token
|
||||||
|
|
||||||
|
bp = Blueprint('api', __name__, url_prefix='/api/v1')
|
||||||
|
bp.register_blueprint(users.bp)
|
||||||
|
bp.register_blueprint(songs.bp)
|
||||||
|
bp.register_blueprint(token.bp)
|
||||||
75
latest version/api/api_auth.py
Normal file
75
latest version/api/api_auth.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from core.api_user import APIUser
|
||||||
|
from core.error import ArcError, NoAccess, PostError
|
||||||
|
from core.sql import Connect
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from .api_code import error_return
|
||||||
|
|
||||||
|
|
||||||
|
def role_required(request, powers=[]):
|
||||||
|
'''api token验证,写成了修饰器'''
|
||||||
|
def decorator(view):
|
||||||
|
@functools.wraps(view)
|
||||||
|
def wrapped_view(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
request.json # 检查请求json格式
|
||||||
|
except:
|
||||||
|
return error_return(PostError('Payload must be a valid json', api_error_code=-1), 400)
|
||||||
|
|
||||||
|
if not 'Token' in request.headers:
|
||||||
|
return error_return(PostError('No token', api_error_code=-1), 401)
|
||||||
|
|
||||||
|
user = APIUser()
|
||||||
|
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
|
||||||
|
user.user_id = 0
|
||||||
|
elif powers == []:
|
||||||
|
# 无powers则非本地权限(API_TOKEN规定的)无法访问
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
else:
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user.c = c
|
||||||
|
user.select_user_id_from_api_token(
|
||||||
|
request.headers['Token'])
|
||||||
|
user.select_role_and_powers()
|
||||||
|
|
||||||
|
if not any([y in [x.power_name for x in user.role.powers] for y in powers]):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e, 401)
|
||||||
|
|
||||||
|
return view(user, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped_view
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def request_json_handle(request, required_keys=[], optional_keys=[]):
|
||||||
|
'''
|
||||||
|
提取post参数,返回dict,写成了修饰器\
|
||||||
|
parameters: \
|
||||||
|
`request`: `Request` - 当前请求\
|
||||||
|
`required_keys`: `list` - 必须的参数\
|
||||||
|
`optional_keys`: `list` - 可选的参数
|
||||||
|
'''
|
||||||
|
|
||||||
|
def decorator(view):
|
||||||
|
@functools.wraps(view)
|
||||||
|
def wrapped_view(*args, **kwargs):
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for key in required_keys:
|
||||||
|
if key not in request.json:
|
||||||
|
return error_return(PostError('Missing parameter: ' + key, api_error_code=-100))
|
||||||
|
data[key] = request.json[key]
|
||||||
|
|
||||||
|
for key in optional_keys:
|
||||||
|
if key in request.json:
|
||||||
|
data[key] = request.json[key]
|
||||||
|
|
||||||
|
return view(data, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped_view
|
||||||
|
return decorator
|
||||||
32
latest version/api/api_code.py
Normal file
32
latest version/api/api_code.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from core.error import ArcError
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
default_error = ArcError('Unknown Error')
|
||||||
|
|
||||||
|
|
||||||
|
CODE_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',
|
||||||
|
-200: 'No permission',
|
||||||
|
-201: 'Wrong username or password',
|
||||||
|
-202: 'User is banned',
|
||||||
|
-203: 'Username exists',
|
||||||
|
-204: 'Email address exists',
|
||||||
|
-999: 'Unknown error'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def success_return(data: dict = {}, status: int = 200, msg: str = ''):
|
||||||
|
return jsonify({'code': 0, 'data': data, 'msg': msg}), status
|
||||||
|
|
||||||
|
|
||||||
|
def error_return(e: 'ArcError' = default_error, status: int = 200):
|
||||||
|
return jsonify({'code': e.api_error_code, 'data': {} if e.extra_data is None else e.extra_data, 'msg': CODE_MSG[e.api_error_code] if e.message is None else e.message}), status
|
||||||
2
latest version/api/constant.py
Normal file
2
latest version/api/constant.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
class Constant:
|
||||||
|
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']
|
||||||
48
latest version/api/songs.py
Normal file
48
latest version/api/songs.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from core.error import ArcError, NoData
|
||||||
|
from core.song import Song
|
||||||
|
from core.sql import Connect, Query, Sql
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .api_auth import request_json_handle, role_required
|
||||||
|
from .api_code import error_return, success_return
|
||||||
|
from .constant import Constant
|
||||||
|
|
||||||
|
bp = Blueprint('songs', __name__, url_prefix='/songs')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<string:song_id>', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_song_info'])
|
||||||
|
def songs_song_get(user, song_id):
|
||||||
|
'''查询歌曲信息'''
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
s = Song(c, song_id).select()
|
||||||
|
return success_return(s.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_song_info'])
|
||||||
|
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
|
||||||
|
def songs_get(data, user):
|
||||||
|
'''查询全歌曲信息'''
|
||||||
|
A = ['song_id', 'name']
|
||||||
|
B = ['song_id', 'name', 'rating_pst',
|
||||||
|
'rating_prs', 'rating_ftr', 'rating_byn']
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
query = Query(A, A, B).from_data(data)
|
||||||
|
x = Sql(c).select('chart', query=query)
|
||||||
|
r = []
|
||||||
|
for i in x:
|
||||||
|
r.append(Song(c).from_list(i))
|
||||||
|
|
||||||
|
if not r:
|
||||||
|
raise NoData(api_error_code=-2)
|
||||||
|
|
||||||
|
return success_return([x.to_dict() for x in r])
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
57
latest version/api/token.py
Normal file
57
latest version/api/token.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
|
from core.api_user import APIUser
|
||||||
|
from core.error import ArcError, PostError
|
||||||
|
from core.sql import Connect
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .api_auth import request_json_handle, role_required
|
||||||
|
from .api_code import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('token', __name__, url_prefix='/token')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['POST'])
|
||||||
|
@request_json_handle(request, required_keys=['auth'])
|
||||||
|
def token_post(data):
|
||||||
|
'''
|
||||||
|
登录,获取token\
|
||||||
|
{'auth': base64('<user_id>:<password>')}
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
auth_decode = bytes.decode(b64decode(data['auth']))
|
||||||
|
except:
|
||||||
|
return error_return(PostError(api_error_code=-100))
|
||||||
|
if not ':' in auth_decode:
|
||||||
|
return error_return(PostError(api_error_code=-100))
|
||||||
|
name, password = auth_decode.split(':', 1)
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = APIUser(c)
|
||||||
|
user.login(name, password, request.remote_addr)
|
||||||
|
return success_return({'token': user.token, 'user_id': user.user_id})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['GET'])
|
||||||
|
@role_required(request, ['select_me', 'select'])
|
||||||
|
def token_get(user):
|
||||||
|
'''判断登录有效性'''
|
||||||
|
return success_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['DELETE'])
|
||||||
|
@role_required(request, ['change_me', 'select_me', 'select'])
|
||||||
|
def token_delete(user):
|
||||||
|
'''登出'''
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user.c = c
|
||||||
|
user.logout()
|
||||||
|
return success_return()
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
150
latest version/api/users.py
Normal file
150
latest version/api/users.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
from core.error import ArcError, InputError, NoAccess, NoData
|
||||||
|
from core.score import Potential, UserScoreList
|
||||||
|
from core.sql import Connect, Query, Sql
|
||||||
|
from core.user import UserInfo, UserRegister
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .api_auth import request_json_handle, role_required
|
||||||
|
from .api_code import error_return, success_return
|
||||||
|
from .constant import Constant
|
||||||
|
|
||||||
|
bp = Blueprint('users', __name__, url_prefix='/users')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['POST'])
|
||||||
|
@role_required(request, ['change'])
|
||||||
|
@request_json_handle(request, ['name', 'password', 'email'])
|
||||||
|
def users_post(data, _):
|
||||||
|
'''注册一个用户'''
|
||||||
|
with Connect() as c:
|
||||||
|
new_user = UserRegister(c)
|
||||||
|
try:
|
||||||
|
new_user.set_name(data['name'])
|
||||||
|
new_user.set_password(data['password'])
|
||||||
|
new_user.set_email(data['email'])
|
||||||
|
new_user.register()
|
||||||
|
return success_return({'user_id': new_user.user_id, 'user_code': new_user.user_code})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['GET'])
|
||||||
|
@role_required(request, ['select'])
|
||||||
|
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
|
||||||
|
def users_get(data, user):
|
||||||
|
'''查询全用户信息'''
|
||||||
|
A = ['user_id', 'name', 'user_code']
|
||||||
|
B = ['user_id', 'name', 'user_code', 'join_date',
|
||||||
|
'rating_ptt', 'time_played', 'ticket', 'world_rank_score']
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
query = Query(A, A, B).from_data(data)
|
||||||
|
x = Sql(c).select('user', query=query)
|
||||||
|
r = []
|
||||||
|
for i in x:
|
||||||
|
r.append(UserInfo(c).from_list(i))
|
||||||
|
|
||||||
|
if not r:
|
||||||
|
raise NoData(api_error_code=-2)
|
||||||
|
|
||||||
|
return success_return([{
|
||||||
|
'user_id': x.user_id,
|
||||||
|
'name': x.name,
|
||||||
|
'join_date': x.join_date,
|
||||||
|
'user_code': x.user_code,
|
||||||
|
'rating_ptt': x.rating_ptt/100,
|
||||||
|
'character_id': x.character.character_id,
|
||||||
|
'is_char_uncapped': x.character.is_uncapped,
|
||||||
|
'is_char_uncapped_override': x.character.is_uncapped_override,
|
||||||
|
'is_hide_rating': x.is_hide_rating,
|
||||||
|
'ticket': x.ticket
|
||||||
|
} for x in r])
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:user_id>', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_me'])
|
||||||
|
def users_user_get(user, user_id):
|
||||||
|
'''查询用户信息'''
|
||||||
|
if user_id <= 0:
|
||||||
|
return error_return(InputError(api_error_code=-4))
|
||||||
|
# 查别人需要select权限
|
||||||
|
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
u = UserInfo(c, user_id)
|
||||||
|
return success_return(u.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:user_id>/b30', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_me'])
|
||||||
|
def users_user_b30_get(user, user_id):
|
||||||
|
'''查询用户b30'''
|
||||||
|
if user_id <= 0:
|
||||||
|
return error_return(InputError(api_error_code=-4))
|
||||||
|
# 查别人需要select权限
|
||||||
|
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserScoreList(c, UserInfo(c, user_id))
|
||||||
|
x.query.limit = 30
|
||||||
|
x.select_from_user()
|
||||||
|
r = x.to_dict_list()
|
||||||
|
rating_sum = sum([i.rating for i in x.scores])
|
||||||
|
return success_return({'user_id': user_id, 'b30_ptt': rating_sum / 30, 'data': r})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:user_id>/best', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_me'])
|
||||||
|
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
|
||||||
|
def users_user_best_get(data, user, user_id):
|
||||||
|
'''查询用户所有best成绩'''
|
||||||
|
if user_id <= 0:
|
||||||
|
return error_return(InputError(api_error_code=-4))
|
||||||
|
# 查别人需要select权限
|
||||||
|
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserScoreList(c, UserInfo(c, user_id))
|
||||||
|
x.query.from_data(data)
|
||||||
|
x.select_from_user()
|
||||||
|
r = x.to_dict_list()
|
||||||
|
return success_return({'user_id': user_id, 'data': r})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:user_id>/r30', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_me'])
|
||||||
|
def users_user_r30_get(user, user_id):
|
||||||
|
'''查询用户r30'''
|
||||||
|
|
||||||
|
if user_id <= 0:
|
||||||
|
return error_return(InputError(api_error_code=-4))
|
||||||
|
# 查别人需要select权限
|
||||||
|
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
p = Potential(c, UserInfo(c, user_id))
|
||||||
|
return success_return({'user_id': user_id, 'r10_ptt': p.recent_10 / 10, 'data': p.recent_30_to_dict_list()})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
0
latest version/core/__init__.py
Normal file
0
latest version/core/__init__.py
Normal file
134
latest version/core/api_user.py
Normal file
134
latest version/core/api_user.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
from hashlib import sha256
|
||||||
|
from os import urandom
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from .error import NoAccess, NoData, UserBan
|
||||||
|
from .user import UserOnline
|
||||||
|
|
||||||
|
|
||||||
|
class Power:
|
||||||
|
def __init__(self, c=None):
|
||||||
|
self.c = c
|
||||||
|
self.power_id: int = None
|
||||||
|
self.power_name: str = None
|
||||||
|
self.caption: str = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, d: dict, c=None) -> 'Power':
|
||||||
|
p = cls(c)
|
||||||
|
p.power_id = d['power_id']
|
||||||
|
p.power_name = d['power_name']
|
||||||
|
p.caption = d['caption']
|
||||||
|
return p
|
||||||
|
|
||||||
|
def select_from_name(self, power_name: str) -> 'Power':
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Role:
|
||||||
|
def __init__(self, c=None):
|
||||||
|
self.c = c
|
||||||
|
self.role_id: int = None
|
||||||
|
self.role_name: str = None
|
||||||
|
self.caption: str = None
|
||||||
|
|
||||||
|
self.powers: list = None
|
||||||
|
|
||||||
|
def has_power(self, power_name: str) -> bool:
|
||||||
|
'''判断role是否有power'''
|
||||||
|
for i in self.powers:
|
||||||
|
if i.power_name == power_name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def select_from_id(self, role_id: int = None) -> 'Role':
|
||||||
|
'''用role_id查询role'''
|
||||||
|
if role_id is not None:
|
||||||
|
self.role_id = role_id
|
||||||
|
self.c.execute('''select role_name, caption from role where role_id = :a''',
|
||||||
|
{'a': self.role_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The role `%s` does not exist.' %
|
||||||
|
self.role_id, api_error_code=-200)
|
||||||
|
self.role_name = x[0]
|
||||||
|
self.caption = x[1]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_powers(self) -> None:
|
||||||
|
'''查询role的全部powers'''
|
||||||
|
self.powers = []
|
||||||
|
self.c.execute('''select * from power where power_id in (select power_id from role_power where role_id=:a)''', {
|
||||||
|
'a': self.role_id})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
for i in x:
|
||||||
|
self.powers.append(Power.from_dict(
|
||||||
|
{'power_id': i[0], 'power_name': i[1], 'caption': i[2]}, self.c))
|
||||||
|
|
||||||
|
|
||||||
|
class APIUser(UserOnline):
|
||||||
|
def __init__(self, c=None, user_id=None) -> None:
|
||||||
|
super().__init__(c, user_id)
|
||||||
|
self.api_token: str = None
|
||||||
|
self.role: 'Role' = None
|
||||||
|
|
||||||
|
self.ip: str = None
|
||||||
|
|
||||||
|
def select_role(self) -> None:
|
||||||
|
'''查询user的role'''
|
||||||
|
self.c.execute('''select role_id from user_role where user_id = :a''',
|
||||||
|
{'a': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
self.role = Role(self.c)
|
||||||
|
if x is None:
|
||||||
|
# 默认role为user
|
||||||
|
self.role.role_id = 1
|
||||||
|
else:
|
||||||
|
self.role.role_id = int(x[0])
|
||||||
|
self.role.select_from_id()
|
||||||
|
|
||||||
|
def select_role_and_powers(self) -> None:
|
||||||
|
'''查询user的role,以及role的powers'''
|
||||||
|
self.select_role()
|
||||||
|
self.role.select_powers()
|
||||||
|
|
||||||
|
def select_user_id_from_api_token(self, api_token: str = None) -> None:
|
||||||
|
if api_token is not None:
|
||||||
|
self.api_token = api_token
|
||||||
|
self.c.execute('''select user_id from api_login where token = :token''', {
|
||||||
|
'token': self.api_token})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoAccess('No token', api_error_code=-1)
|
||||||
|
self.user_id = x[0]
|
||||||
|
|
||||||
|
def logout(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''delete from api_login where user_id=?''', (self.user_id,))
|
||||||
|
|
||||||
|
def login(self, name: str = None, password: str = None, ip: str = None) -> None:
|
||||||
|
if name is not None:
|
||||||
|
self.name = name
|
||||||
|
if password is not None:
|
||||||
|
self.password = password
|
||||||
|
if ip is not None:
|
||||||
|
self.ip = ip
|
||||||
|
self.c.execute('''select user_id, password from user where name = :a''', {
|
||||||
|
'a': self.name})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The user `%s` does not exist.' %
|
||||||
|
self.name, api_error_code=-201)
|
||||||
|
if x[1] == '':
|
||||||
|
raise UserBan('The user `%s` is banned.' % self.name)
|
||||||
|
if self.hash_pwd != x[1]:
|
||||||
|
raise NoAccess('The password is incorrect.', api_error_code=-201)
|
||||||
|
|
||||||
|
self.user_id = x[0]
|
||||||
|
now = int(time() * 1000)
|
||||||
|
self.token = sha256(
|
||||||
|
(str(self.user_id) + str(now)).encode("utf8") + urandom(8)).hexdigest()
|
||||||
|
|
||||||
|
self.logout()
|
||||||
|
self.c.execute('''insert into api_login values(?,?,?,?)''',
|
||||||
|
(self.user_id, self.token, now, self.ip))
|
||||||
374
latest version/core/character.py
Normal file
374
latest version/core/character.py
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
from setting import Config
|
||||||
|
from .error import ArcError, InputError, NoData, ItemNotEnough
|
||||||
|
from .constant import Constant
|
||||||
|
from .item import Item, ItemCore
|
||||||
|
|
||||||
|
|
||||||
|
class Level:
|
||||||
|
mid_level = 20
|
||||||
|
min_level = 1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.max_level = None
|
||||||
|
self.level = None
|
||||||
|
self.exp = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def level_exp(self):
|
||||||
|
return Constant.LEVEL_STEPS[self.level]
|
||||||
|
|
||||||
|
def add_exp(self, exp_addition: float):
|
||||||
|
# 添加经验计算
|
||||||
|
|
||||||
|
exp = self.exp + exp_addition
|
||||||
|
|
||||||
|
if exp >= Constant.LEVEL_STEPS[self.max_level]:
|
||||||
|
self.exp = Constant.LEVEL_STEPS[self.max_level]
|
||||||
|
self.level = self.max_level
|
||||||
|
|
||||||
|
a = []
|
||||||
|
b = []
|
||||||
|
for i in Constant.LEVEL_STEPS:
|
||||||
|
a.append(i)
|
||||||
|
b.append(Constant.LEVEL_STEPS[i])
|
||||||
|
|
||||||
|
if exp < b[0]: # 向下溢出,是异常状态,不该被try捕获,不然数据库无法回滚
|
||||||
|
raise ValueError('EXP value error.')
|
||||||
|
|
||||||
|
i = len(a) - 1
|
||||||
|
while exp < b[i]:
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
self.exp = exp
|
||||||
|
self.level = a[i]
|
||||||
|
|
||||||
|
|
||||||
|
class Skill:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.skill_id = None
|
||||||
|
self.skill_id_uncap = None
|
||||||
|
self.skill_unlock_level = None
|
||||||
|
self.skill_requires_uncap = None
|
||||||
|
|
||||||
|
|
||||||
|
class Core(Item):
|
||||||
|
item_type = 'core'
|
||||||
|
|
||||||
|
def __init__(self, core_type: str = '', amount: int = 0) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.item_id = core_type
|
||||||
|
self.amount = amount
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {'core_type': self.item_id, 'amount': self.amount}
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterValue:
|
||||||
|
def __init__(self, start: float = 0, mid: float = 0, end: float = 0) -> None:
|
||||||
|
self.set_parameter(start, mid, end)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calc_char_value_30(level, stata, statb, lva=20, lvb=30):
|
||||||
|
# 计算21~30级搭档数值,返回浮点数
|
||||||
|
return (level - lva) * (statb - stata) / (lvb - lva) + stata
|
||||||
|
|
||||||
|
def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0):
|
||||||
|
self.start = start
|
||||||
|
self.mid = mid
|
||||||
|
self.end = end
|
||||||
|
|
||||||
|
def get_value(self, level: Level):
|
||||||
|
if level.min_level <= level.level <= level.mid_level:
|
||||||
|
return self._calc_char_value_20(level.level, self.start, self.mid)
|
||||||
|
elif level.mid_level < level.level <= level.max_level:
|
||||||
|
return self._calc_char_value_30(level.level, self.mid, self.end)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class Character:
|
||||||
|
database_table_name = None
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.character_id = None
|
||||||
|
self.name = None
|
||||||
|
self.char_type = None
|
||||||
|
self.is_uncapped = None
|
||||||
|
self.is_uncapped_override = None
|
||||||
|
self.skill = Skill()
|
||||||
|
self.level = Level()
|
||||||
|
self.frag = CharacterValue()
|
||||||
|
self.prog = CharacterValue()
|
||||||
|
self.overdrive = CharacterValue()
|
||||||
|
self.uncap_cores = []
|
||||||
|
self.voice = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skill_id_displayed(self) -> str:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def uncap_cores_to_dict(self):
|
||||||
|
return [x.to_dict() for x in self.uncap_cores]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_uncapped_displayed(self) -> bool:
|
||||||
|
'''对外显示的uncap状态'''
|
||||||
|
return False if self.is_uncapped_override else self.is_uncapped
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_base_character(self) -> bool:
|
||||||
|
# 应该是只有对立这样
|
||||||
|
return self.character_id == 1
|
||||||
|
|
||||||
|
|
||||||
|
class UserCharacter(Character):
|
||||||
|
'''
|
||||||
|
用户角色类\
|
||||||
|
property: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
database_table_name = 'user_char_full' if Config.CHARACTER_FULL_UNLOCK else 'user_char'
|
||||||
|
|
||||||
|
def __init__(self, c, character_id=None, user=None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.character_id = character_id
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skill_id_displayed(self) -> str:
|
||||||
|
'''对外显示的技能id'''
|
||||||
|
if self.is_uncapped_displayed and self.skill.skill_id_uncap:
|
||||||
|
return self.skill.skill_id_uncap
|
||||||
|
elif self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level:
|
||||||
|
return self.skill.skill_id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def select_character_core(self):
|
||||||
|
# 获取此角色所需核心
|
||||||
|
self.c.execute(
|
||||||
|
'''select item_id, amount from char_item where character_id = ? and type="core"''', (self.character_id,))
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
self.uncap_cores = []
|
||||||
|
for i in x:
|
||||||
|
self.uncap_cores.append(Core(i[0], i[1]))
|
||||||
|
|
||||||
|
def select_character_uncap_condition(self, user=None):
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
# 获取此角色的觉醒信息
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name,
|
||||||
|
{'a': self.user.user_id, 'b': self.character_id})
|
||||||
|
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('The character of the user does not exist.')
|
||||||
|
|
||||||
|
self.is_uncapped = x[0] == 1
|
||||||
|
self.is_uncapped_override = x[1] == 1
|
||||||
|
|
||||||
|
def select_character_info(self, user=None):
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
# 获取所给用户此角色信息
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select * from %s a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''' % self.database_table_name,
|
||||||
|
(self.user.user_id, self.character_id))
|
||||||
|
|
||||||
|
y = self.c.fetchone()
|
||||||
|
if y is None:
|
||||||
|
raise NoData('The character of the user does not exist.')
|
||||||
|
|
||||||
|
self.name = y[7]
|
||||||
|
self.char_type = y[22]
|
||||||
|
self.is_uncapped = y[4] == 1
|
||||||
|
self.is_uncapped_override = y[5] == 1
|
||||||
|
self.level.level = y[2]
|
||||||
|
self.level.exp = y[3]
|
||||||
|
self.level.max_level = y[8]
|
||||||
|
self.frag.set_parameter(y[9], y[12], y[15])
|
||||||
|
self.prog.set_parameter(y[10], y[13], y[16])
|
||||||
|
self.overdrive.set_parameter(y[11], y[14], y[17])
|
||||||
|
self.skill.skill_id = y[18]
|
||||||
|
self.skill.skill_id_uncap = y[21]
|
||||||
|
self.skill.skill_unlock_level = y[19]
|
||||||
|
self.skill.skill_requires_uncap = y[20] == 1
|
||||||
|
|
||||||
|
if self.character_id == 21 or self.character_id == 46:
|
||||||
|
self.voice = [0, 1, 2, 3, 100, 1000, 1001]
|
||||||
|
|
||||||
|
self.select_character_core()
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
if self.char_type is None:
|
||||||
|
self.select_character_info(self.user)
|
||||||
|
r = {'base_character': self.is_base_character,
|
||||||
|
"is_uncapped_override": self.is_uncapped_override,
|
||||||
|
"is_uncapped": self.is_uncapped,
|
||||||
|
"uncap_cores": self.uncap_cores_to_dict(),
|
||||||
|
"char_type": self.char_type,
|
||||||
|
"skill_id_uncap": self.skill.skill_id_uncap,
|
||||||
|
"skill_requires_uncap": self.skill.skill_requires_uncap,
|
||||||
|
"skill_unlock_level": self.skill.skill_unlock_level,
|
||||||
|
"skill_id": self.skill.skill_id,
|
||||||
|
"overdrive": self.overdrive.get_value(self.level),
|
||||||
|
"prog": self.overdrive.get_value(self.level),
|
||||||
|
"frag": self.overdrive.get_value(self.level),
|
||||||
|
"level_exp": self.level.level_exp,
|
||||||
|
"exp": self.level.exp,
|
||||||
|
"level": self.level.level,
|
||||||
|
"name": self.name,
|
||||||
|
"character_id": self.character_id
|
||||||
|
}
|
||||||
|
if self.voice:
|
||||||
|
r['voice'] = self.voice
|
||||||
|
if self.character_id == 55:
|
||||||
|
r['fatalis_is_limited'] = True # emmmmmmm
|
||||||
|
if self.character_id in [1, 6, 7, 17, 18, 24, 32, 35, 52]:
|
||||||
|
r['base_character_id'] = 1
|
||||||
|
return r
|
||||||
|
|
||||||
|
def change_uncap_override(self, user=None):
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
# 切换觉醒状态
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name,
|
||||||
|
{'a': self.user.user_id, 'b': self.character_id})
|
||||||
|
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None or x[0] == 0:
|
||||||
|
raise ArcError('Unknown Error')
|
||||||
|
|
||||||
|
self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
|
||||||
|
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id})
|
||||||
|
|
||||||
|
self.c.execute('''update %s set is_uncapped_override = :a where user_id = :b and character_id = :c''' % self.database_table_name, {
|
||||||
|
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id, 'c': self.character_id})
|
||||||
|
|
||||||
|
self.is_uncapped_override = x[1] == 0
|
||||||
|
|
||||||
|
def character_uncap(self, user=None):
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
# 觉醒角色
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
if Config.CHARACTER_FULL_UNLOCK:
|
||||||
|
# 全解锁了你觉醒个鬼啊
|
||||||
|
raise ArcError('All characters are available.')
|
||||||
|
|
||||||
|
if not self.uncap_cores:
|
||||||
|
self.select_character_core()
|
||||||
|
|
||||||
|
if self.is_uncapped is None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select is_uncapped from user_char where user_id=? and character_id=?''', (self.user.user_id, self.character_id))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x and x[0] == 1:
|
||||||
|
raise ArcError('The character has been uncapped.')
|
||||||
|
elif self.is_uncapped:
|
||||||
|
raise ArcError('The character has been uncapped.')
|
||||||
|
|
||||||
|
for i in self.uncap_cores:
|
||||||
|
self.c.execute(
|
||||||
|
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (self.user.user_id, i.item_id))
|
||||||
|
y = self.c.fetchone()
|
||||||
|
if not y or i.amount > y[0]:
|
||||||
|
raise ItemNotEnough('The cores are not enough.')
|
||||||
|
|
||||||
|
for i in self.uncap_cores:
|
||||||
|
ItemCore(self.c, i, reverse=True).user_claim_item(self.user)
|
||||||
|
|
||||||
|
self.c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
|
||||||
|
(self.user.user_id, self.character_id))
|
||||||
|
|
||||||
|
self.is_uncapped = True
|
||||||
|
self.is_uncapped_override = False
|
||||||
|
|
||||||
|
def upgrade(self, user=None, exp_addition: float = 0) -> None:
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
# 升级角色
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
if exp_addition == 0:
|
||||||
|
return None
|
||||||
|
if Config.CHARACTER_FULL_UNLOCK:
|
||||||
|
# 全解锁了你升级个鬼啊
|
||||||
|
raise ArcError('All characters are available.')
|
||||||
|
|
||||||
|
if self.level.exp is None:
|
||||||
|
self.select_character_info(self.user)
|
||||||
|
|
||||||
|
if self.is_uncapped is None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select is_uncapped from user_char where user_id=? and character_id=?''', (self.user.user_id, self.character_id))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.is_uncapped = x[0] == 1
|
||||||
|
|
||||||
|
self.level.max_level = 30 if self.is_uncapped else 20
|
||||||
|
self.level.add_exp(exp_addition)
|
||||||
|
|
||||||
|
self.c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
|
||||||
|
(self.level.level, self.level.exp, self.user.user_id, self.character_id))
|
||||||
|
|
||||||
|
def upgrade_by_core(self, user=None, core=None):
|
||||||
|
'''
|
||||||
|
以太之滴升级,注意这里core.amount应该是负数\
|
||||||
|
parameter: `user` - `User`类或子类的实例\
|
||||||
|
`core` - `ItemCore`类或子类的实例
|
||||||
|
'''
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
if not core:
|
||||||
|
raise InputError('No `core_generic`.')
|
||||||
|
if core.item_id != 'core_generic':
|
||||||
|
raise ArcError('Core type error.')
|
||||||
|
|
||||||
|
if core.amount >= 0:
|
||||||
|
raise InputError(
|
||||||
|
'The amount of `core_generic` should be negative.')
|
||||||
|
|
||||||
|
core.user_claim_item(self.user)
|
||||||
|
self.upgrade(self.user, - core.amount * Constant.CORE_EXP)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCharacterList:
|
||||||
|
'''
|
||||||
|
用户拥有角色列表类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
database_table_name = 'user_char_full' if Config.CHARACTER_FULL_UNLOCK else 'user_char'
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.characters: list = []
|
||||||
|
|
||||||
|
def select_user_characters(self):
|
||||||
|
self.c.execute(
|
||||||
|
'''select character_id from %s where user_id=?''' % self.database_table_name, (self.user.user_id,))
|
||||||
|
x = self.c.fetchall()
|
||||||
|
self.characters: list = []
|
||||||
|
if x:
|
||||||
|
for i in x:
|
||||||
|
self.characters.append(UserCharacter(self.c, i[0], self.user))
|
||||||
|
|
||||||
|
def select_characters_info(self):
|
||||||
|
for i in self.characters:
|
||||||
|
i.select_character_info(self.user)
|
||||||
84
latest version/core/constant.py
Normal file
84
latest version/core/constant.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from setting import Config
|
||||||
|
|
||||||
|
|
||||||
|
class Constant:
|
||||||
|
|
||||||
|
BAN_TIME = [1, 3, 7, 15, 31]
|
||||||
|
|
||||||
|
MAX_STAMINA = 12
|
||||||
|
|
||||||
|
STAMINA_RECOVER_TICK = 1800000
|
||||||
|
|
||||||
|
CORE_EXP = 250
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
ETO_UNCAP_BONUS_PROGRESS = 7
|
||||||
|
LUNA_UNCAP_BONUS_PROGRESS = 7
|
||||||
|
AYU_UNCAP_BONUS_PROGRESS = 5
|
||||||
|
SKILL_FATALIS_WORLD_LOCKED_TIME = 3600000
|
||||||
|
|
||||||
|
MAX_FRIEND_COUNT = 50
|
||||||
|
|
||||||
|
# You can change this to make another PTT mechanism.
|
||||||
|
BEST30_WEIGHT = 1 / 40
|
||||||
|
RECENT10_WEIGHT = 1 / 40
|
||||||
|
|
||||||
|
WORLD_MAP_FOLDER_PATH = './database/map/'
|
||||||
|
SONG_FILE_FOLDER_PATH = './database/songs/'
|
||||||
|
SQLITE_DATABASE_PATH = './database/arcaea_database.db'
|
||||||
|
|
||||||
|
DOWNLOAD_TIMES_LIMIT = Config.DOWNLOAD_TIMES_LIMIT
|
||||||
|
DOWNLOAD_TIME_GAP_LIMIT = Config.DOWNLOAD_TIME_GAP_LIMIT
|
||||||
|
DOWNLOAD_LINK_PREFIX = Config.DOWNLOAD_LINK_PREFIX
|
||||||
|
|
||||||
|
LINK_PLAY_UNLOCK_LENGTH = 512 # Units: bytes
|
||||||
|
LINK_PLAY_TIMEOUT = 10 # Units: seconds
|
||||||
|
|
||||||
|
COURSE_STAMINA_COST = 4
|
||||||
|
|
||||||
|
# Well, I can't say a word when I see this.
|
||||||
|
SHIT_DATA_OF_616 = [
|
||||||
|
(0x0015F0, 0x00B032), (0x014C9A, 0x014408), (0x062585, 0x02783B),
|
||||||
|
(0x02429E, 0x0449A4), (0x099C9C,
|
||||||
|
0x07CFB4), (0x0785BF, 0x019B2C),
|
||||||
|
(0x0EFF43, 0x0841BF), (0x07C88B,
|
||||||
|
0x0DE9FC), (0x000778, 0x064815),
|
||||||
|
(0x0E62E3, 0x079F02), (0x0188FE,
|
||||||
|
0x0923EB), (0x0E06CD, 0x0E1A26),
|
||||||
|
(0x00669E, 0x0C8BE1), (0x0BEB7A, 0x05D635), (0x040E6F,
|
||||||
|
0x0B465B), (0x0568EC, 0x07ED2B),
|
||||||
|
(0x189614, 0x00A3D2), (0x62D98D,
|
||||||
|
0x45E5CA), (0x6D8769, 0x473F0E),
|
||||||
|
(0x922E4F, 0x667D6C), (0x021F5C,
|
||||||
|
0x298839), (0x2A1201, 0x49FB7E),
|
||||||
|
(0x158B81, 0x8D905D), (0x2253A5,
|
||||||
|
0x7E7067), (0x3BEF79, 0x9368E9),
|
||||||
|
(0x00669E, 0x0C8BE1), (0x0BEB7A,
|
||||||
|
0x05D635), (0x040E6F, 0x0B465B),
|
||||||
|
(0x756276, 0x55CD57), (0x130055, 0x7010E7), (0x55E28D,
|
||||||
|
0x4477FB), (0x5E99CB, 0x81060E),
|
||||||
|
(0x7F43A4, 0x8FEC56), (0x69412F,
|
||||||
|
0x32735C), (0x8FF846, 0x14B5A1),
|
||||||
|
(0x8716BE, 0x5C78BE), (0x62ED0E,
|
||||||
|
0x348E4B), (0x4B20C8, 0x56E0C3),
|
||||||
|
(0x0AF6BC, 0x872441), (0x8825BC,
|
||||||
|
0x94B315), (0x792784, 0x5B2C8E),
|
||||||
|
(0x1AE3A7, 0x688E97), (0x0D630F,
|
||||||
|
0x06BE78), (0x792784, 0x5B2C8E),
|
||||||
|
(0x314869, 0x41CCC1), (0x311934, 0x24DD94), (0x190EDB,
|
||||||
|
0x33993D), (0x25F5C5, 0x15FAE6),
|
||||||
|
(0x18CA10, 0x1B761A), (0x51BE82,
|
||||||
|
0x120089), (0x51D3B6, 0x2C29A2),
|
||||||
|
(0x402075, 0x4A89B2), (0x00697B,
|
||||||
|
0x0E6497), (0x6D872D, 0x618AE7),
|
||||||
|
(0x3DC0BE, 0x4E2AC8), (0x8C6ACF,
|
||||||
|
0x9776CF), (0x84673B, 0x5CA060),
|
||||||
|
(0x4B05EC, 0x97FDFE), (0x207258,
|
||||||
|
0x02BB9B), (0x20A9EE, 0x1BA4BB),
|
||||||
|
(0x503D21, 0x6A41D0), (0x1C256C,
|
||||||
|
0x6DD3BC), (0x6E4E0C, 0x89FDAA), (0x3C5F95, 0x3BA786)
|
||||||
|
]
|
||||||
|
UNKNOWN_SHIT = [0XFEA5, 0X7BF653, 0X46BEA7B,
|
||||||
|
0X8BFB04, 0X5D6FC5, 0X237206D, 0XA3DEE, 0XA35687B]
|
||||||
289
latest version/core/course.py
Normal file
289
latest version/core/course.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from .error import NoData
|
||||||
|
from .song import Chart
|
||||||
|
from .item import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
class CourseChart(Chart):
|
||||||
|
def __init__(self, c=None, song_id: str = None, difficulty: int = None) -> None:
|
||||||
|
super().__init__(c, song_id, difficulty)
|
||||||
|
self.flag_as_hidden: bool = None
|
||||||
|
|
||||||
|
def from_dict(self, d: dict) -> 'CourseChart':
|
||||||
|
self.song_id = d['id']
|
||||||
|
self.difficulty = d['difficulty']
|
||||||
|
self.flag_as_hidden = d.get('flag_as_hidden', False)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'id': self.song_id,
|
||||||
|
'difficulty': self.difficulty,
|
||||||
|
'flag_as_hidden': self.flag_as_hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
def insert_course_chart(self, course_id: str, song_index: int) -> None:
|
||||||
|
self.c.execute('insert into course_chart values (?,?,?,?,?)',
|
||||||
|
(course_id, self.song_id, self.difficulty, self.flag_as_hidden, song_index))
|
||||||
|
|
||||||
|
|
||||||
|
class Course:
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.course_id: str = None
|
||||||
|
self.course_name: str = None
|
||||||
|
self.dan_name: str = None
|
||||||
|
self.style: int = None
|
||||||
|
self.gauge_requirement: str = None
|
||||||
|
self.flag_as_hidden_when_requirements_not_met: bool = None
|
||||||
|
self.can_start: bool = None # 这有什么鬼用?
|
||||||
|
|
||||||
|
self.requirements: list = []
|
||||||
|
self.charts: list = [None, None, None, None]
|
||||||
|
self.items: list = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
if self.course_name is None:
|
||||||
|
self.select_course()
|
||||||
|
if not self.items:
|
||||||
|
self.select_course_item()
|
||||||
|
if not self.charts:
|
||||||
|
self.select_course_chart()
|
||||||
|
return {
|
||||||
|
'course_id': self.course_id,
|
||||||
|
'course_name': self.course_name,
|
||||||
|
'dan_name': self.dan_name,
|
||||||
|
'style': self.style,
|
||||||
|
'gauge_requirement': self.gauge_requirement,
|
||||||
|
'flag_as_hidden_when_requirements_not_met': self.flag_as_hidden_when_requirements_not_met,
|
||||||
|
'can_start': self.can_start,
|
||||||
|
'requirements': [{'value': x, 'type': 'course'} for x in self.requirements],
|
||||||
|
'songs': [x.to_dict() for x in self.charts],
|
||||||
|
'rewards': [str(x) for x in self.items]
|
||||||
|
}
|
||||||
|
|
||||||
|
def from_dict(self, d: dict) -> 'Course':
|
||||||
|
self.course_id = d['course_id']
|
||||||
|
self.course_name = d.get('course_name', '')
|
||||||
|
self.dan_name = d.get('dan_name', '')
|
||||||
|
self.style = d.get('style', 1)
|
||||||
|
self.gauge_requirement = d.get('gauge_requirement', 'default')
|
||||||
|
self.flag_as_hidden_when_requirements_not_met = d.get(
|
||||||
|
'flag_as_hidden_when_requirements_not_met', False)
|
||||||
|
self.can_start = d.get('can_start', True)
|
||||||
|
self.requirements = [x['value'] for x in d.get('requirements', [])]
|
||||||
|
self.charts = [CourseChart(self.c).from_dict(x)
|
||||||
|
for x in d.get('songs', [])]
|
||||||
|
self.items = [ItemFactory.from_str(x, self.c)
|
||||||
|
for x in d.get('rewards', [])]
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def from_list(self, l: list) -> 'Course':
|
||||||
|
self.course_id = l[0]
|
||||||
|
self.course_name = l[1]
|
||||||
|
self.dan_name = l[2]
|
||||||
|
self.style = l[3] if l[3] else 1
|
||||||
|
self.gauge_requirement = l[4] if l[4] else 'default'
|
||||||
|
self.flag_as_hidden_when_requirements_not_met = l[5] == 1
|
||||||
|
self.can_start = l[6] == 1
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_course(self, course_id: str = None) -> 'Course':
|
||||||
|
if course_id is not None:
|
||||||
|
self.course_id = course_id
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from course where course_id = ?''', (self.course_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The course `%s` is not found.' % self.course_id)
|
||||||
|
return self.from_list(x)
|
||||||
|
|
||||||
|
def select_course_chart(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from course_chart where course_id = ?''', (self.course_id,))
|
||||||
|
|
||||||
|
for i in self.c.fetchall():
|
||||||
|
self.charts[i[4]] = CourseChart(self.c).from_dict({
|
||||||
|
'id': i[1],
|
||||||
|
'difficulty': i[2],
|
||||||
|
'flag_as_hidden': i[3] == 1
|
||||||
|
})
|
||||||
|
|
||||||
|
def select_course_requirement(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select required_id from course_requirement where course_id = ?''', (self.course_id,))
|
||||||
|
self.requirements = [x[0] for x in self.c.fetchall()]
|
||||||
|
|
||||||
|
def select_course_item(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from course_item where course_id = ?''', (self.course_id,))
|
||||||
|
self.items = [ItemFactory.from_dict({
|
||||||
|
'item_id': x[1],
|
||||||
|
'type': x[2],
|
||||||
|
'amount': x[3] if x[3] else 1,
|
||||||
|
}, self.c) for x in self.c.fetchall()]
|
||||||
|
|
||||||
|
def insert_course(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''insert into course values (?,?,?,?,?,?,?)''', (self.course_id, self.course_name, self.dan_name, self.style, self.gauge_requirement, self.flag_as_hidden_when_requirements_not_met, self.can_start))
|
||||||
|
|
||||||
|
def insert_course_item(self) -> None:
|
||||||
|
for i in self.items:
|
||||||
|
self.c.execute('''insert into course_item values (?,?,?,?)''',
|
||||||
|
(self.course_id, i.item_id, i.item_type, i.amount))
|
||||||
|
|
||||||
|
def insert_course_chart(self) -> None:
|
||||||
|
for i, x in enumerate(self.charts):
|
||||||
|
x.insert_course_chart(self.course_id, i)
|
||||||
|
|
||||||
|
def insert_course_requirement(self) -> None:
|
||||||
|
for i in self.requirements:
|
||||||
|
self.c.execute('''insert into course_requirement values (?,?)''',
|
||||||
|
(self.course_id, i))
|
||||||
|
|
||||||
|
def insert_all(self) -> None:
|
||||||
|
self.insert_course()
|
||||||
|
self.insert_course_item()
|
||||||
|
self.insert_course_chart()
|
||||||
|
self.insert_course_requirement()
|
||||||
|
|
||||||
|
|
||||||
|
class UserCourse(Course):
|
||||||
|
'''
|
||||||
|
用户课题类\
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.is_completed: bool = False
|
||||||
|
self.high_score: int = None
|
||||||
|
self.best_clear_type: int = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
r = super().to_dict()
|
||||||
|
if self.is_completed is None:
|
||||||
|
self.select_user_course()
|
||||||
|
r.update({
|
||||||
|
'is_completed': self.is_completed,
|
||||||
|
'high_score': self.high_score,
|
||||||
|
'best_clear_type': self.best_clear_type
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
|
||||||
|
def select_user_course(self, course_id: str = None) -> None:
|
||||||
|
if course_id is not None:
|
||||||
|
self.course_id = course_id
|
||||||
|
self.c.execute('''select * from user_course where user_id = ? and course_id = ?''',
|
||||||
|
(self.user.user_id, self.course_id))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
self.is_completed = False
|
||||||
|
self.high_score = 0
|
||||||
|
self.best_clear_type = 0
|
||||||
|
else:
|
||||||
|
self.is_completed = True
|
||||||
|
self.high_score = x[2]
|
||||||
|
self.best_clear_type = x[3]
|
||||||
|
|
||||||
|
def insert_user_course(self) -> None:
|
||||||
|
self.c.execute('''insert into user_course values (?,?,?,?)''',
|
||||||
|
(self.user.user_id, self.course_id, self.high_score, self.best_clear_type))
|
||||||
|
|
||||||
|
def update_user_course(self) -> None:
|
||||||
|
self.c.execute('''update user_course set high_score = ?, best_clear_type = ? where user_id = ? and course_id = ?''',
|
||||||
|
(self.high_score, self.best_clear_type, self.user.user_id, self.course_id))
|
||||||
|
|
||||||
|
|
||||||
|
class UserCourseList:
|
||||||
|
'''
|
||||||
|
用户课题列表类\
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.courses: list = []
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list:
|
||||||
|
return [x.to_dict() for x in self.courses]
|
||||||
|
|
||||||
|
def select_all(self) -> None:
|
||||||
|
self.c.execute('''select * from course''')
|
||||||
|
self.courses = [UserCourse(self.c, self.user).from_list(x)
|
||||||
|
for x in self.c.fetchall()]
|
||||||
|
for i in self.courses:
|
||||||
|
i.select_user_course()
|
||||||
|
i.select_course_chart()
|
||||||
|
i.select_course_item()
|
||||||
|
i.select_course_requirement()
|
||||||
|
|
||||||
|
|
||||||
|
class CoursePlay(UserCourse):
|
||||||
|
'''
|
||||||
|
课题模式打歌类,联动UserPlay\
|
||||||
|
parameter: `user` - `UserOnline`类或子类的实例\
|
||||||
|
'user_play` - `UserPlay`类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None, user_play=None) -> None:
|
||||||
|
super().__init__(c, user)
|
||||||
|
self.user_play = user_play
|
||||||
|
|
||||||
|
self.score: int = None
|
||||||
|
self.clear_type: int = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'rewards': [x.to_dict() for x in self.items],
|
||||||
|
"current_stamina": self.user.stamina.stamina,
|
||||||
|
"max_stamina_ts": self.user.stamina.max_stamina_ts,
|
||||||
|
'user_course_banners': self.user.course_banners
|
||||||
|
}
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
'''课题模式更新'''
|
||||||
|
if self.user_play.health < 0:
|
||||||
|
# 你挂了
|
||||||
|
self.user_play.course_play_state = 5
|
||||||
|
self.score = 0
|
||||||
|
self.clear_type = 0
|
||||||
|
self.user_play.update_play_state_for_course()
|
||||||
|
return None
|
||||||
|
self.user_play.course_play_state += 1
|
||||||
|
self.score += self.user_play.score
|
||||||
|
from .score import Score
|
||||||
|
if Score.get_song_state(self.clear_type) > Score.get_song_state(self.user_play.clear_type):
|
||||||
|
self.clear_type = self.user_play.clear_type
|
||||||
|
self.user_play.update_play_state_for_course()
|
||||||
|
|
||||||
|
if self.user_play.course_play_state == 4:
|
||||||
|
self.user.select_user_about_stamina()
|
||||||
|
self.select_course_item()
|
||||||
|
for i in self.items:
|
||||||
|
i.user_claim_item(self.user)
|
||||||
|
|
||||||
|
self.select_user_course()
|
||||||
|
if not self.is_completed:
|
||||||
|
self.high_score = self.score
|
||||||
|
self.best_clear_type = self.clear_type
|
||||||
|
self.is_completed = True
|
||||||
|
self.insert_user_course()
|
||||||
|
return None
|
||||||
|
|
||||||
|
flag = False
|
||||||
|
if self.score > self.high_score:
|
||||||
|
self.high_score = self.score
|
||||||
|
flag = True
|
||||||
|
if Score.get_song_state(self.clear_type) > Score.get_song_state(self.best_clear_type):
|
||||||
|
self.best_clear_type = self.clear_type
|
||||||
|
flag = True
|
||||||
|
if flag:
|
||||||
|
self.update_user_course()
|
||||||
189
latest version/core/download.py
Normal file
189
latest version/core/download.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import os
|
||||||
|
from functools import lru_cache
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
from .error import NoAccess
|
||||||
|
from .user import User
|
||||||
|
from .util import get_file_md5, md5
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=8192)
|
||||||
|
def get_song_file_md5(song_id: str, file_name: str) -> str:
|
||||||
|
path = os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id, file_name)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return None
|
||||||
|
return get_file_md5(path)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_songfile():
|
||||||
|
'''初始化歌曲数据的md5信息'''
|
||||||
|
get_song_file_md5.cache_clear()
|
||||||
|
x = DownloadList()
|
||||||
|
x.url_flag = False
|
||||||
|
x.add_songs()
|
||||||
|
del x
|
||||||
|
|
||||||
|
|
||||||
|
class UserDownload:
|
||||||
|
'''
|
||||||
|
用户下载类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.song_id: str = None
|
||||||
|
self.file_name: str = None
|
||||||
|
|
||||||
|
self.file_path: str = None
|
||||||
|
self.token: str = None
|
||||||
|
self.token_time: int = None
|
||||||
|
|
||||||
|
def clear_user_download(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''delete from user_download where user_id = :a and time <= :b''', {'a': self.user.user_id, 'b': int(time()) - 24*3600})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_limited(self) -> bool:
|
||||||
|
'''是否达到用户最大下载量'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select count(*) from user_download where user_id = :a''', {'a': self.user.user_id})
|
||||||
|
y = self.c.fetchone()
|
||||||
|
return y is not None and y[0] > Constant.DOWNLOAD_TIMES_LIMIT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
'''链接是否有效且未过期'''
|
||||||
|
return int(time()) - self.token_time <= Constant.DOWNLOAD_TIME_GAP_LIMIT and self.song_id+'/'+self.file_name == self.file_path
|
||||||
|
|
||||||
|
def insert_user_download(self) -> None:
|
||||||
|
'''记录下载信息'''
|
||||||
|
self.c.execute('''insert into user_download values(:a,:b,:c)''', {
|
||||||
|
'a': self.user.user_id, 'b': self.token, 'c': int(time())})
|
||||||
|
|
||||||
|
def select_from_token(self, token: str = None) -> None:
|
||||||
|
if token is not None:
|
||||||
|
self.token = token
|
||||||
|
self.c.execute('''select * from download_token where token = :t limit 1''',
|
||||||
|
{'t': self.token})
|
||||||
|
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoAccess('The token `%s` is not valid.' % self.token)
|
||||||
|
self.user = User()
|
||||||
|
self.user.user_id = x[0]
|
||||||
|
self.song_id = x[1]
|
||||||
|
self.file_name = x[2]
|
||||||
|
self.token_time = x[4]
|
||||||
|
|
||||||
|
def generate_token(self) -> None:
|
||||||
|
self.token_time = int(time())
|
||||||
|
self.token = md5(str(self.user.user_id) + self.song_id +
|
||||||
|
self.file_name + str(self.token_time))
|
||||||
|
|
||||||
|
def insert_download_token(self) -> None:
|
||||||
|
'''将数据插入数据库,让这个下载链接可用'''
|
||||||
|
self.c.execute('''insert into download_token values(:a,:b,:c,:d,:e)''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song_id, 'c': self.file_name, 'd': self.token, 'e': self.token_time})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str:
|
||||||
|
'''生成下载链接'''
|
||||||
|
if self.token is None:
|
||||||
|
self.generate_token()
|
||||||
|
self.insert_download_token()
|
||||||
|
if Constant.DOWNLOAD_LINK_PREFIX:
|
||||||
|
prefix = Constant.DOWNLOAD_LINK_PREFIX
|
||||||
|
if prefix[-1] != '/':
|
||||||
|
prefix += '/'
|
||||||
|
return prefix + self.song_id + '/' + self.file_name + '?t=' + self.token
|
||||||
|
else:
|
||||||
|
from flask import url_for
|
||||||
|
return url_for('download', file_path=self.song_id + '/' + self.file_name, t=self.token, _external=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hash(self) -> str:
|
||||||
|
return get_song_file_md5(self.song_id, self.file_name)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadList(UserDownload):
|
||||||
|
'''
|
||||||
|
下载列表类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__(c, user)
|
||||||
|
|
||||||
|
self.song_ids: list = None
|
||||||
|
self.url_flag: bool = None
|
||||||
|
|
||||||
|
self.downloads: list = []
|
||||||
|
self.urls: dict = {}
|
||||||
|
|
||||||
|
def clear_download_token_from_song(self, song_id: str) -> None:
|
||||||
|
self.c.execute('''delete from download_token where user_id=:a and song_id=:b''', {
|
||||||
|
'a': self.user.user_id, 'b': song_id})
|
||||||
|
|
||||||
|
def add_one_song(self, song_id: str) -> None:
|
||||||
|
if self.url_flag:
|
||||||
|
self.clear_download_token_from_song(song_id)
|
||||||
|
dir_list = os.listdir(os.path.join(
|
||||||
|
Constant.SONG_FILE_FOLDER_PATH, song_id))
|
||||||
|
|
||||||
|
re = {}
|
||||||
|
for i in dir_list:
|
||||||
|
if os.path.isfile(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg', 'video.mp4', 'video_audio.ogg']:
|
||||||
|
x = UserDownload(self.c, self.user)
|
||||||
|
# self.downloads.append(x) # 这实际上没有用
|
||||||
|
x.song_id = song_id
|
||||||
|
x.file_name = i
|
||||||
|
if i == 'base.ogg':
|
||||||
|
if 'audio' not in re:
|
||||||
|
re['audio'] = {}
|
||||||
|
|
||||||
|
re['audio']["checksum"] = x.hash
|
||||||
|
if self.url_flag:
|
||||||
|
re['audio']["url"] = x.url
|
||||||
|
elif i == '3.ogg':
|
||||||
|
if 'audio' not in re:
|
||||||
|
re['audio'] = {}
|
||||||
|
|
||||||
|
if self.url_flag:
|
||||||
|
re['audio']['3'] = {"checksum": x.hash, "url": x.url}
|
||||||
|
else:
|
||||||
|
re['audio']['3'] = {"checksum": x.hash}
|
||||||
|
elif i == 'video.mp4' or i == 'video_audio.ogg':
|
||||||
|
if 'additional_files' not in re:
|
||||||
|
re['additional_files'] = []
|
||||||
|
|
||||||
|
if self.url_flag:
|
||||||
|
re['additional_files'].append(
|
||||||
|
{"checksum": x.hash, "url": x.url, 'file_name': i})
|
||||||
|
else:
|
||||||
|
re['additional_files'].append(
|
||||||
|
{"checksum": x.hash, 'file_name': i})
|
||||||
|
else:
|
||||||
|
if 'chart' not in re:
|
||||||
|
re['chart'] = {}
|
||||||
|
|
||||||
|
if self.url_flag:
|
||||||
|
re['chart'][i[0]] = {"checksum": x.hash, "url": x.url}
|
||||||
|
else:
|
||||||
|
re['chart'][i[0]] = {"checksum": x.hash}
|
||||||
|
|
||||||
|
self.urls.update({song_id: re})
|
||||||
|
|
||||||
|
def add_songs(self, song_ids: list = None) -> None:
|
||||||
|
'''添加一个或多个歌曲到下载列表,若`song_ids`为空,则添加所有歌曲'''
|
||||||
|
if song_ids is not None:
|
||||||
|
self.song_ids = song_ids
|
||||||
|
|
||||||
|
x = self.song_ids if self.song_ids else os.listdir(
|
||||||
|
Constant.SONG_FILE_FOLDER_PATH)
|
||||||
|
for i in x:
|
||||||
|
if os.path.isdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, i)):
|
||||||
|
self.add_one_song(i)
|
||||||
90
latest version/core/error.py
Normal file
90
latest version/core/error.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
class ArcError(Exception):
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
self.message = message
|
||||||
|
self.error_code = error_code
|
||||||
|
self.api_error_code = api_error_code
|
||||||
|
self.extra_data = extra_data
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return repr(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class InputError(ArcError):
|
||||||
|
# 输入类型错误
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-100, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class DataExist(ArcError):
|
||||||
|
# 数据存在
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoData(ArcError):
|
||||||
|
# 数据不存在
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-2, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class PostError(ArcError):
|
||||||
|
# 缺少输入
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-100, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class UserBan(ArcError):
|
||||||
|
# 用户封禁
|
||||||
|
def __init__(self, message=None, error_code=121, api_error_code=-202, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemNotEnough(ArcError):
|
||||||
|
# 物品数量不足
|
||||||
|
def __init__(self, message=None, error_code=-6, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemUnavailable(ArcError):
|
||||||
|
# 物品不可用
|
||||||
|
def __init__(self, message=None, error_code=-6, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class RedeemUnavailable(ArcError):
|
||||||
|
# 兑换码不可用
|
||||||
|
def __init__(self, message=None, error_code=505, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class MapLocked(ArcError):
|
||||||
|
# 地图锁定
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class StaminaNotEnough(ArcError):
|
||||||
|
# 体力不足
|
||||||
|
def __init__(self, message=None, error_code=107, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class TicketNotEnough(ArcError):
|
||||||
|
# 记忆源点不足
|
||||||
|
def __init__(self, message=None, error_code=-6, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class FriendError(ArcError):
|
||||||
|
# 好友系统出错
|
||||||
|
def __init__(self, message=None, error_code=108, api_error_code=-999, extra_data=None) -> None:
|
||||||
|
super().__init__(message, error_code, api_error_code, extra_data)
|
||||||
|
|
||||||
|
|
||||||
|
class NoAccess(ArcError):
|
||||||
|
# 无权限
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Timeout(ArcError):
|
||||||
|
# 超时
|
||||||
|
pass
|
||||||
390
latest version/core/item.py
Normal file
390
latest version/core/item.py
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
from .error import InputError, ItemNotEnough, ItemUnavailable, NoData
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
|
||||||
|
class Item:
|
||||||
|
item_type = None
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.item_id = None
|
||||||
|
self.__amount = None
|
||||||
|
self.is_available = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount(self):
|
||||||
|
return self.__amount
|
||||||
|
|
||||||
|
@amount.setter
|
||||||
|
def amount(self, value: int):
|
||||||
|
self.__amount = int(value)
|
||||||
|
|
||||||
|
def to_dict(self, has_is_available: bool = False) -> dict:
|
||||||
|
r = {
|
||||||
|
'id': self.item_id,
|
||||||
|
'amount': self.amount,
|
||||||
|
'type': self.item_type
|
||||||
|
}
|
||||||
|
if has_is_available:
|
||||||
|
r['is_available'] = self.is_available
|
||||||
|
return r
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
# parameter: user - User类或子类的实例
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserItem(Item):
|
||||||
|
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.user = None
|
||||||
|
|
||||||
|
def select(self, user=None):
|
||||||
|
'''
|
||||||
|
查询用户item\
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
if user is not None:
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select amount from user_item where user_id=? and item_id=? and type=?''',
|
||||||
|
(self.user.user_id, self.item_id, self.item_type))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.amount = x[0] if x[0] else 1
|
||||||
|
else:
|
||||||
|
self.amount = 0
|
||||||
|
|
||||||
|
|
||||||
|
class NormalItem(UserItem):
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
self.user = user
|
||||||
|
if not self.is_available:
|
||||||
|
self.c.execute(
|
||||||
|
'''select is_available from item where item_id=? and type=?''', (self.item_id, self.item_type))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
if x[0] == 0:
|
||||||
|
self.is_available = False
|
||||||
|
raise ItemUnavailable('The item is unavailable.')
|
||||||
|
else:
|
||||||
|
self.is_available = True
|
||||||
|
else:
|
||||||
|
raise NoData('No item data.')
|
||||||
|
|
||||||
|
self.c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
|
||||||
|
(self.user.user_id, self.item_id, self.item_type))
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
self.c.execute('''insert into user_item values(:a,:b,:c,1)''',
|
||||||
|
{'a': self.user.user_id, 'b': self.item_id, 'c': self.item_type})
|
||||||
|
|
||||||
|
|
||||||
|
class PositiveItem(UserItem):
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
'''添加或使用用户item,注意是+amount'''
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select amount from user_item where user_id=? and item_id=? and type=?''',
|
||||||
|
(self.user.user_id, self.item_id, self.item_type))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
if x[0] + self.amount < 0: # 数量不足
|
||||||
|
raise ItemNotEnough(
|
||||||
|
'The user does not have enough `%s`.' % self.item_id)
|
||||||
|
self.c.execute('''update user_item set amount=? where user_id=? and item_id=? and type=?''',
|
||||||
|
(x[0]+self.amount, self.user.user_id, self.item_id, self.item_type))
|
||||||
|
else:
|
||||||
|
if self.amount < 0: # 添加数量错误
|
||||||
|
raise InputError(
|
||||||
|
'The amount of `%s` is wrong.' % self.item_id)
|
||||||
|
self.c.execute('''insert into user_item values(?,?,?,?)''',
|
||||||
|
(self.user.user_id, self.item_id, self.item_type, self.amount))
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCore(PositiveItem):
|
||||||
|
item_type = 'core'
|
||||||
|
|
||||||
|
def __init__(self, c, core=None, reverse=False) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.is_available = True
|
||||||
|
if core:
|
||||||
|
self.item_id = core.item_id
|
||||||
|
self.amount = - core.amount if reverse else core.amount
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.item_id + '_' + str(self.amount)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCharacter(UserItem):
|
||||||
|
item_type = 'character'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
def set_id(self, character_id: str) -> None:
|
||||||
|
# 将name: str转为character_id: int存到item_id里
|
||||||
|
if character_id.isdigit():
|
||||||
|
self.item_id = int(character_id)
|
||||||
|
else:
|
||||||
|
self.c.execute(
|
||||||
|
'''select character_id from character where name=?''', (character_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.item_id = x[0]
|
||||||
|
else:
|
||||||
|
raise NoData('No character `%s`.' % character_id)
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
if not isinstance(self.item_id, int):
|
||||||
|
self.set_id(self.item_id)
|
||||||
|
|
||||||
|
self.c.execute(
|
||||||
|
'''select exists(select * from user_char where user_id=? and character_id=?)''', (user.user_id, self.item_id))
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
self.c.execute(
|
||||||
|
'''insert into user_char values(?,?,1,0,0,0)''', (user.user_id, self.item_id))
|
||||||
|
|
||||||
|
|
||||||
|
class Memory(UserItem):
|
||||||
|
item_type = 'memory'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
self.c.execute(
|
||||||
|
'''select ticket from user where user_id=?''', (user.user_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is not None:
|
||||||
|
self.c.execute('''update user set ticket=? where user_id=?''',
|
||||||
|
(x[0]+self.amount, user.user_id))
|
||||||
|
else:
|
||||||
|
raise NoData('The ticket of the user is null.')
|
||||||
|
|
||||||
|
|
||||||
|
class Fragment(UserItem):
|
||||||
|
item_type = 'fragment'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return 'fragment' + str(self.amount)
|
||||||
|
|
||||||
|
|
||||||
|
class Anni5tix(PositiveItem):
|
||||||
|
item_type = 'anni5tix'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
|
||||||
|
class WorldSong(NormalItem):
|
||||||
|
item_type = 'world_song'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
|
||||||
|
class WorldUnlock(NormalItem):
|
||||||
|
item_type = 'world_unlock'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
|
||||||
|
class CourseBanner(NormalItem):
|
||||||
|
item_type = 'course_banner'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.is_available = True
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.item_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Single(NormalItem):
|
||||||
|
item_type = 'single'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
|
||||||
|
|
||||||
|
class Pack(NormalItem):
|
||||||
|
item_type = 'pack'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
|
||||||
|
|
||||||
|
class ProgBoost(UserItem):
|
||||||
|
item_type = 'prog_boost_300'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
'''
|
||||||
|
世界模式prog_boost\
|
||||||
|
parameters: `user` - `UserOnline`类或子类的实例
|
||||||
|
'''
|
||||||
|
user.update_user_one_column('prog_boost', 1)
|
||||||
|
|
||||||
|
|
||||||
|
class Stamina6(UserItem):
|
||||||
|
item_type = 'stamina6'
|
||||||
|
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
|
||||||
|
def user_claim_item(self, user):
|
||||||
|
'''
|
||||||
|
世界模式记忆源点或残片买体力+6\
|
||||||
|
顺手清一下世界模式过载状态
|
||||||
|
'''
|
||||||
|
user.select_user_about_stamina()
|
||||||
|
user.stamina.stamina += 6
|
||||||
|
user.stamina.update()
|
||||||
|
user.update_user_one_column('world_mode_locked_end_ts', -1)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemFactory:
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
def get_item(self, item_type: str):
|
||||||
|
'''
|
||||||
|
根据item_type实例化对应的item类
|
||||||
|
return: Item类或子类的实例
|
||||||
|
'''
|
||||||
|
if item_type == 'core':
|
||||||
|
return ItemCore(self.c)
|
||||||
|
elif item_type == 'character':
|
||||||
|
return ItemCharacter(self.c)
|
||||||
|
elif item_type == 'memory':
|
||||||
|
return Memory(self.c)
|
||||||
|
elif item_type == 'anni5tix':
|
||||||
|
return Anni5tix(self.c)
|
||||||
|
elif item_type == 'world_song':
|
||||||
|
return WorldSong(self.c)
|
||||||
|
elif item_type == 'world_unlock':
|
||||||
|
return WorldUnlock(self.c)
|
||||||
|
elif item_type == 'single':
|
||||||
|
return Single(self.c)
|
||||||
|
elif item_type == 'pack':
|
||||||
|
return Pack(self.c)
|
||||||
|
elif item_type == 'fragment':
|
||||||
|
return Fragment(self.c)
|
||||||
|
elif item_type == 'prog_boost_300':
|
||||||
|
return ProgBoost(self.c)
|
||||||
|
elif item_type == 'stamina6':
|
||||||
|
return Stamina6(self.c)
|
||||||
|
elif item_type == 'course_banner':
|
||||||
|
return CourseBanner(self.c)
|
||||||
|
else:
|
||||||
|
raise InputError('The item type `%s` is wrong.' % item_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, d: dict, c=None):
|
||||||
|
'''注意这里没有处理character_id的转化,是为了世界模式的map服务的'''
|
||||||
|
if 'item_type' in d:
|
||||||
|
item_type = d['item_type']
|
||||||
|
elif 'type' in d:
|
||||||
|
item_type = d['type']
|
||||||
|
else:
|
||||||
|
raise InputError('The dict of item is wrong.')
|
||||||
|
i = cls().get_item(item_type)
|
||||||
|
if c is not None:
|
||||||
|
i.c = c
|
||||||
|
if 'item_id' in d:
|
||||||
|
i.item_id = d['item_id']
|
||||||
|
elif 'id' in d:
|
||||||
|
i.item_id = d['id']
|
||||||
|
else:
|
||||||
|
i.item_id = item_type
|
||||||
|
i.amount = d.get('amount', 1)
|
||||||
|
i.is_available = d.get('is_available', True)
|
||||||
|
return i
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, s: str, c=None):
|
||||||
|
if s.startswith('fragment'):
|
||||||
|
item_type = 'fragment'
|
||||||
|
item_id = 'fragment'
|
||||||
|
amount = int(s[8:])
|
||||||
|
elif s.startswith('core'):
|
||||||
|
item_type = 'core'
|
||||||
|
x = s.split('_')
|
||||||
|
item_id = x[0] + '_' + x[1]
|
||||||
|
amount = int(x[-1])
|
||||||
|
elif s.startswith('course_banner'):
|
||||||
|
item_type = 'course_banner'
|
||||||
|
item_id = s
|
||||||
|
amount = 1
|
||||||
|
else:
|
||||||
|
raise InputError('The string of item is wrong.')
|
||||||
|
i = cls().get_item(item_type)
|
||||||
|
if c is not None:
|
||||||
|
i.c = c
|
||||||
|
i.item_id = item_id
|
||||||
|
i.amount = amount
|
||||||
|
i.is_available = True
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
class UserItemList:
|
||||||
|
'''
|
||||||
|
用户的item列表\
|
||||||
|
注意只能查在user_item里面的,character不行\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.items: list = []
|
||||||
|
|
||||||
|
def select_from_type(self, item_type: str) -> 'UserItemList':
|
||||||
|
'''
|
||||||
|
根据item_type搜索用户的item
|
||||||
|
'''
|
||||||
|
if Config.WORLD_SONG_FULL_UNLOCK and item_type == 'world_song' or Config.WORLD_SCENERY_FULL_UNLOCK and item_type == 'world_unlock':
|
||||||
|
self.c.execute(
|
||||||
|
'''select item_id from item where type=?''', (item_type,))
|
||||||
|
else:
|
||||||
|
self.c.execute('''select item_id, amount from user_item where type = :a''', {
|
||||||
|
'a': item_type})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
return self
|
||||||
|
|
||||||
|
self.items: list = []
|
||||||
|
for i in x:
|
||||||
|
if len(i) > 1:
|
||||||
|
amount = i[1] if i[1] else 0
|
||||||
|
else:
|
||||||
|
amount = 1
|
||||||
|
self.items.append(ItemFactory.from_dict(
|
||||||
|
{'item_id': i[0], 'amount': amount, 'item_type': item_type}, self.c))
|
||||||
|
return self
|
||||||
147
latest version/core/linkplay.py
Normal file
147
latest version/core/linkplay.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
from core.error import ArcError, Timeout
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
from .user import UserInfo
|
||||||
|
|
||||||
|
|
||||||
|
def get_song_unlock(client_song_map: dict) -> bytes:
|
||||||
|
'''处理可用歌曲bit,返回bytes'''
|
||||||
|
|
||||||
|
user_song_unlock = [0] * Constant.LINK_PLAY_UNLOCK_LENGTH
|
||||||
|
for i in range(0, Constant.LINK_PLAY_UNLOCK_LENGTH*2, 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)
|
||||||
|
|
||||||
|
|
||||||
|
class Player(UserInfo):
|
||||||
|
def __init__(self, c=None, user_id=None) -> None:
|
||||||
|
super().__init__(c, user_id)
|
||||||
|
self.player_id: int = 0
|
||||||
|
self.token: int = 0
|
||||||
|
self.key: bytes = None
|
||||||
|
|
||||||
|
self.__song_unlock: bytes = None
|
||||||
|
self.client_song_map: dict = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'userId': self.user_id,
|
||||||
|
'playerId': str(self.player_id),
|
||||||
|
'token': str(self.token),
|
||||||
|
'key': (b64encode(self.key)).decode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def song_unlock(self) -> bytes:
|
||||||
|
if self.__song_unlock is None:
|
||||||
|
self.get_song_unlock()
|
||||||
|
return self.__song_unlock
|
||||||
|
|
||||||
|
def get_song_unlock(self, client_song_map: dict = None) -> bytes:
|
||||||
|
if client_song_map is not None:
|
||||||
|
self.client_song_map = client_song_map
|
||||||
|
self.__song_unlock = get_song_unlock(self.client_song_map)
|
||||||
|
|
||||||
|
|
||||||
|
class Room:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.room_id: int = 0
|
||||||
|
self.room_code: str = 'AAAA00'
|
||||||
|
|
||||||
|
self.song_unlock: bytes = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'roomId': str(self.room_id),
|
||||||
|
'roomCode': self.room_code,
|
||||||
|
'orderedAllowedSongs': (b64encode(self.song_unlock)).decode()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LocalMultiPlayer:
|
||||||
|
def __init__(self, conn=None) -> None:
|
||||||
|
self.conn = conn
|
||||||
|
self.user: 'Player' = None
|
||||||
|
self.room: 'Room' = None
|
||||||
|
|
||||||
|
self.data_recv: tuple = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return dict(self.room.to_dict(), **self.user.to_dict())
|
||||||
|
|
||||||
|
def data_swap(self, data: tuple) -> tuple:
|
||||||
|
self.conn.send(data)
|
||||||
|
if self.conn.poll(Constant.LINK_PLAY_TIMEOUT):
|
||||||
|
self.data_recv = self.conn.recv()
|
||||||
|
if self.data_recv[0] != 0:
|
||||||
|
raise ArcError('Link Play error.', self.data_recv[0])
|
||||||
|
else:
|
||||||
|
raise Timeout(
|
||||||
|
'Timeout when waiting for data from local udp server.')
|
||||||
|
|
||||||
|
def create_room(self, user: 'Player' = None) -> None:
|
||||||
|
'''创建房间'''
|
||||||
|
if user is not None:
|
||||||
|
self.user = user
|
||||||
|
user.select_user_one_column('name')
|
||||||
|
self.data_swap((1, self.user.name, self.user.song_unlock))
|
||||||
|
self.room = Room()
|
||||||
|
self.room.room_code = self.data_recv[1]
|
||||||
|
self.room.room_id = self.data_recv[2]
|
||||||
|
self.room.song_unlock = self.user.song_unlock
|
||||||
|
self.user.token = self.data_recv[3]
|
||||||
|
self.user.key = self.data_recv[4]
|
||||||
|
self.user.player_id = self.data_recv[5]
|
||||||
|
|
||||||
|
def join_room(self, room: 'Room' = None, user: 'Player' = None) -> None:
|
||||||
|
'''加入房间'''
|
||||||
|
if user is not None:
|
||||||
|
self.user = user
|
||||||
|
if room is not None:
|
||||||
|
self.room = room
|
||||||
|
|
||||||
|
self.user.select_user_one_column('name')
|
||||||
|
self.data_swap(
|
||||||
|
(2, self.user.name, self.user.song_unlock, room.room_code))
|
||||||
|
self.room.room_code = self.data_recv[1]
|
||||||
|
self.room.room_id = self.data_recv[2]
|
||||||
|
self.room.song_unlock = self.data_recv[6]
|
||||||
|
self.user.token = self.data_recv[3]
|
||||||
|
self.user.key = self.data_recv[4]
|
||||||
|
self.user.player_id = self.data_recv[5]
|
||||||
|
|
||||||
|
def update_room(self, user: 'Player' = None) -> None:
|
||||||
|
'''更新房间'''
|
||||||
|
if user is not None:
|
||||||
|
self.user = user
|
||||||
|
self.data_swap((3, self.user.token))
|
||||||
|
self.room = Room()
|
||||||
|
self.room.room_code = self.data_recv[1]
|
||||||
|
self.room.room_id = self.data_recv[2]
|
||||||
|
self.room.song_unlock = self.data_recv[5]
|
||||||
|
self.user.key = self.data_recv[3]
|
||||||
|
self.user.player_id = self.data_recv[4]
|
||||||
137
latest version/core/present.py
Normal file
137
latest version/core/present.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
|
from core.item import ItemFactory
|
||||||
|
|
||||||
|
from .error import ArcError, NoData
|
||||||
|
|
||||||
|
|
||||||
|
class Present:
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.present_id: str = None
|
||||||
|
self.expire_ts: int = None
|
||||||
|
self.description: str = None
|
||||||
|
|
||||||
|
self.items: list = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_expired(self) -> bool:
|
||||||
|
return self.expire_ts < int(time() * 1000)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'present_id': self.present_id,
|
||||||
|
'expire_ts': self.expire_ts,
|
||||||
|
'description': self.description,
|
||||||
|
'items': [x.to_dict() for x in self.items]
|
||||||
|
}
|
||||||
|
|
||||||
|
def select(self, present_id: str = None) -> None:
|
||||||
|
'''
|
||||||
|
用present_id查询信息
|
||||||
|
'''
|
||||||
|
if present_id:
|
||||||
|
self.present_id = present_id
|
||||||
|
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from present where present_id=:a''', {'a': self.present_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The present `%s` does not exist.' % self.present_id)
|
||||||
|
|
||||||
|
self.expire_ts = x[1] if x[1] else 0
|
||||||
|
self.description = x[2] if x[2] else ''
|
||||||
|
|
||||||
|
def select_items(self) -> None:
|
||||||
|
'''
|
||||||
|
查询奖励的物品
|
||||||
|
'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from present_item where present_id=:a''', {'a': self.present_id})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
raise NoData('The present `%s` does not have any items.' %
|
||||||
|
self.present_id)
|
||||||
|
self.items = [ItemFactory.from_dict({
|
||||||
|
'item_id': i[1],
|
||||||
|
'type': i[2],
|
||||||
|
'amount': i[3] if i[3] else 1
|
||||||
|
}, self.c) for i in x]
|
||||||
|
|
||||||
|
|
||||||
|
class UserPresent(Present):
|
||||||
|
'''
|
||||||
|
用户登录奖励类\
|
||||||
|
忽视了description的多语言\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def delete_user_present(self) -> None:
|
||||||
|
'''
|
||||||
|
删除用户奖励
|
||||||
|
'''
|
||||||
|
self.c.execute('''delete from user_present where user_id=:a and present_id=:b''',
|
||||||
|
{'a': self.user.user_id, 'b': self.present_id})
|
||||||
|
|
||||||
|
def claim_user_present(self, present_id: str = None, user=None) -> None:
|
||||||
|
'''
|
||||||
|
确认并删除用户奖励
|
||||||
|
'''
|
||||||
|
if present_id:
|
||||||
|
self.present_id = present_id
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
if self.expire_ts is None:
|
||||||
|
self.select()
|
||||||
|
if self.items is None:
|
||||||
|
self.select_items()
|
||||||
|
|
||||||
|
self.c.execute('''select exists(select * from user_present where user_id=:a and present_id=:b)''',
|
||||||
|
{'a': self.user.user_id, 'b': self.present_id})
|
||||||
|
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
raise NoData('The present `%s` for the user `%s` does not exist.' % (
|
||||||
|
self.present_id, self.user.user_id))
|
||||||
|
|
||||||
|
self.delete_user_present()
|
||||||
|
|
||||||
|
if self.is_expired:
|
||||||
|
raise ArcError('The present `%s` has expired.' % self.present_id)
|
||||||
|
|
||||||
|
for i in self.items:
|
||||||
|
i.user_claim_item(self.user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserPresentList:
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.presents: list = None
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list:
|
||||||
|
return [x.to_dict() for x in self.presents]
|
||||||
|
|
||||||
|
def select_user_presents(self) -> None:
|
||||||
|
'''
|
||||||
|
查询用户全部奖励
|
||||||
|
'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from present where present_id in (select present_id from user_present where user_id=:a)''', {'a': self.user.user_id})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
self.presents = []
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for i in x:
|
||||||
|
p = UserPresent(self.c, self.user)
|
||||||
|
p.present_id = i[0]
|
||||||
|
p.expire_ts = i[1]
|
||||||
|
p.description = i[2]
|
||||||
|
if not p.is_expired:
|
||||||
|
p.select_items()
|
||||||
|
self.presents.append(p)
|
||||||
158
latest version/core/purchase.py
Normal file
158
latest version/core/purchase.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
|
from .error import NoData, TicketNotEnough
|
||||||
|
from .item import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
class Purchase:
|
||||||
|
'''
|
||||||
|
购买类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.purchase_name: str = None
|
||||||
|
self.price: int = None
|
||||||
|
self.orig_price: int = None
|
||||||
|
self.discount_from: int = None
|
||||||
|
self.discount_to: int = None
|
||||||
|
self.discount_reason: str = None
|
||||||
|
|
||||||
|
self.items: list = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def price_displayed(self) -> int:
|
||||||
|
'''
|
||||||
|
返回显示的价格
|
||||||
|
'''
|
||||||
|
if self.discount_from > 0 and self.discount_to > 0:
|
||||||
|
if self.discount_from <= int(time() * 1000) <= self.discount_to:
|
||||||
|
if self.discount_reason == 'anni5tix':
|
||||||
|
x = ItemFactory(self.c).get_item('anni5tix')
|
||||||
|
x.item_id = 'anni5tix'
|
||||||
|
x.select(self.user)
|
||||||
|
if x.amount >= 1:
|
||||||
|
return 0
|
||||||
|
return self.price
|
||||||
|
return self.orig_price
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
price = self.price_displayed
|
||||||
|
r = {
|
||||||
|
'name': self.purchase_name,
|
||||||
|
'price': price,
|
||||||
|
'orig_price': self.orig_price,
|
||||||
|
'items': [x.to_dict(has_is_available=True) for x in self.items]
|
||||||
|
}
|
||||||
|
if self.discount_from > 0 and self.discount_to > 0:
|
||||||
|
r['discount_from'] = self.discount_from
|
||||||
|
r['discount_to'] = self.discount_to
|
||||||
|
if self.discount_reason == 'anni5tix' and price == 0:
|
||||||
|
r['discount_reason'] = self.discount_reason
|
||||||
|
return r
|
||||||
|
|
||||||
|
def select(self, purchase_name: str = None) -> 'Purchase':
|
||||||
|
'''
|
||||||
|
用purchase_name查询信息
|
||||||
|
'''
|
||||||
|
if purchase_name:
|
||||||
|
self.purchase_name = purchase_name
|
||||||
|
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from purchase where purchase_name=:name''', {'name': purchase_name})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('The purchase `%s` does not exist.' %
|
||||||
|
purchase_name, 501)
|
||||||
|
|
||||||
|
self.price = x[1]
|
||||||
|
self.orig_price = x[2]
|
||||||
|
self.discount_from = x[3] if x[3] else -1
|
||||||
|
self.discount_to = x[4] if x[4] else -1
|
||||||
|
self.discount_reason = x[5] if x[5] else ''
|
||||||
|
self.select_items()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_items(self) -> None:
|
||||||
|
'''从数据库拉取purchase_item数据'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select item_id, type, amount from purchase_item where purchase_name=:a''', {'a': self.purchase_name})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
raise NoData('The items of the purchase `%s` does not exist.' %
|
||||||
|
self.purchase_name, 501)
|
||||||
|
|
||||||
|
self.items = []
|
||||||
|
t = None
|
||||||
|
|
||||||
|
for i in x:
|
||||||
|
if i[0] == self.purchase_name:
|
||||||
|
# 物品排序,否则客户端报错
|
||||||
|
t = ItemFactory.from_dict({
|
||||||
|
'item_id': i[0],
|
||||||
|
'type': i[1],
|
||||||
|
'amount': i[2] if i[2] else 1
|
||||||
|
}, self.c)
|
||||||
|
else:
|
||||||
|
self.items.append(ItemFactory.from_dict({
|
||||||
|
'item_id': i[0],
|
||||||
|
'type': i[1],
|
||||||
|
'amount': i[2] if i[2] else 1
|
||||||
|
}, self.c))
|
||||||
|
if t is not None:
|
||||||
|
self.items = [t] + self.items
|
||||||
|
|
||||||
|
def buy(self) -> None:
|
||||||
|
'''进行购买'''
|
||||||
|
if self.price is None or self.orig_price is None:
|
||||||
|
self.select()
|
||||||
|
if not self.items:
|
||||||
|
self.select_items()
|
||||||
|
self.user.select_user_one_column('ticket', 0)
|
||||||
|
price_used = self.price_displayed
|
||||||
|
|
||||||
|
if self.user.ticket < price_used:
|
||||||
|
raise TicketNotEnough(
|
||||||
|
'The user does not have enough memories.', -6)
|
||||||
|
|
||||||
|
if not(self.orig_price == 0 or self.price == 0 and self.discount_from <= int(time() * 1000) <= self.discount_to):
|
||||||
|
if price_used == 0:
|
||||||
|
x = ItemFactory(self.c).get_item('anni5tix')
|
||||||
|
x.item_id = 'anni5tix'
|
||||||
|
x.amount = -1
|
||||||
|
x.user_claim_item(self.user)
|
||||||
|
else:
|
||||||
|
self.user.ticket -= price_used
|
||||||
|
self.user.update_user_one_column('ticket')
|
||||||
|
|
||||||
|
for i in self.items:
|
||||||
|
i.user_claim_item(self.user)
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseList:
|
||||||
|
'''
|
||||||
|
购买列表类\
|
||||||
|
property: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.purchases: list = []
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list:
|
||||||
|
return [x.to_dict() for x in self.purchases]
|
||||||
|
|
||||||
|
def select_from_type(self, item_type: str) -> 'PurchaseList':
|
||||||
|
self.c.execute('''select purchase_name from purchase_item where type = :a''', {
|
||||||
|
'a': item_type})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
return self
|
||||||
|
|
||||||
|
self.purchases: list = []
|
||||||
|
for i in x:
|
||||||
|
self.purchases.append(Purchase(self.c, self.user).select(i[0]))
|
||||||
|
return self
|
||||||
157
latest version/core/rank.py
Normal file
157
latest version/core/rank.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
from .constant import Constant
|
||||||
|
from .score import UserScore
|
||||||
|
from .song import Chart
|
||||||
|
from .user import UserInfo
|
||||||
|
|
||||||
|
|
||||||
|
class RankList:
|
||||||
|
'''
|
||||||
|
排行榜类\
|
||||||
|
默认limit=20,limit<0认为是all\
|
||||||
|
property: `user` - `User`类或者子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.list: list = []
|
||||||
|
self.song = Chart()
|
||||||
|
self.limit: int = 20
|
||||||
|
self.user = None
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list:
|
||||||
|
return [x.to_dict() for x in self.list]
|
||||||
|
|
||||||
|
def select_top(self) -> None:
|
||||||
|
'''
|
||||||
|
得到top分数表
|
||||||
|
'''
|
||||||
|
if self.limit >= 0:
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit})
|
||||||
|
else:
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty})
|
||||||
|
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
|
||||||
|
rank = 0
|
||||||
|
self.list = []
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
|
|
||||||
|
def select_friend(self, user=None, limit=Constant.MAX_FRIEND_COUNT) -> None:
|
||||||
|
'''
|
||||||
|
得到用户好友分数表
|
||||||
|
'''
|
||||||
|
self.limit = limit
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.c.execute('''select user_id from best_score where user_id in (select :user_id union select user_id_other from friend where user_id_me = :user_id) and song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit''', {
|
||||||
|
'user_id': self.user.user_id, 'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
|
||||||
|
rank = 0
|
||||||
|
self.list = []
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
|
|
||||||
|
def select_me(self, user=None) -> None:
|
||||||
|
'''
|
||||||
|
得到我的排名分数表\
|
||||||
|
尚不清楚这个函数有没有问题
|
||||||
|
'''
|
||||||
|
if user:
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select score, time_played from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty''', {
|
||||||
|
'user_id': self.user.user_id, 'song_id': self.song.song_id, 'difficulty': self.song.difficulty})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.c.execute('''select count(*) from best_score where song_id = :song_id and difficulty = :difficulty and ( score > :score or (score = :score and time_played > :time_played) )''', {
|
||||||
|
'user_id': self.user.user_id, 'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'score': x[0], 'time_played': x[1]})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
myrank = int(x[0]) + 1
|
||||||
|
self.c.execute('''select count(*) from best_score where song_id=:a and difficulty=:b''',
|
||||||
|
{'a': self.song.song_id, 'b': self.song.difficulty})
|
||||||
|
amount = int(self.c.fetchone()[0])
|
||||||
|
|
||||||
|
if myrank <= 4: # 排名在前4
|
||||||
|
self.select_top()
|
||||||
|
elif myrank >= 5 and myrank <= 9999 - self.limit + 4 and amount >= 10000: # 万名内,前面有4个人
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit, 'offset': myrank - 5})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
rank = myrank - 5
|
||||||
|
self.list = []
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
|
|
||||||
|
elif myrank >= 10000: # 万名外
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit - 1, 'offset': 9999-self.limit})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
rank = 9999 - self.limit
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, self.user.user_id))
|
||||||
|
y.song = self.song
|
||||||
|
y.rank = -1
|
||||||
|
self.list.append(y)
|
||||||
|
|
||||||
|
elif amount - myrank < self.limit - 5: # 后方人数不足
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit, 'offset': amount - self.limit})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
rank = amount - self.limit
|
||||||
|
if rank < 0:
|
||||||
|
rank = 0
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
|
||||||
|
'song_id': self.song.song_id, 'difficulty': self.song.difficulty, 'limit': self.limit, 'offset': 9998-self.limit})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
rank = 9998 - self.limit
|
||||||
|
for i in x:
|
||||||
|
rank += 1
|
||||||
|
y = UserScore(self.c, UserInfo(self.c, i[0]))
|
||||||
|
y.song = self.song
|
||||||
|
y.select_score()
|
||||||
|
y.rank = rank
|
||||||
|
self.list.append(y)
|
||||||
94
latest version/core/redeem.py
Normal file
94
latest version/core/redeem.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from .error import NoData, RedeemUnavailable
|
||||||
|
from .item import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
class Redeem:
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.code: str = None
|
||||||
|
self.redeem_type: int = None
|
||||||
|
|
||||||
|
self.items: list = []
|
||||||
|
self.fragment: int = None
|
||||||
|
|
||||||
|
def select(self, code: str = None) -> None:
|
||||||
|
if code:
|
||||||
|
self.code = code
|
||||||
|
self.c.execute('''select * from redeem where code=:a''',
|
||||||
|
{'a': self.code})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The redeem `%s` does not exist.' % self.code, 504)
|
||||||
|
|
||||||
|
self.redeem_type = x[1]
|
||||||
|
|
||||||
|
def select_items(self) -> None:
|
||||||
|
self.c.execute('''select * from redeem_item where code=:a''',
|
||||||
|
{'a': self.code})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if not x:
|
||||||
|
raise NoData(
|
||||||
|
'The redeem `%s` does not have any items.' % self.code)
|
||||||
|
self.items = [ItemFactory.from_dict({
|
||||||
|
'item_id': i[1],
|
||||||
|
'type': i[2],
|
||||||
|
'amount': i[3] if i[3] else 1
|
||||||
|
}, self.c) for i in x]
|
||||||
|
|
||||||
|
|
||||||
|
class UserRedeem(Redeem):
|
||||||
|
'''
|
||||||
|
用户兑换码类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__(c)
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_available(self) -> bool:
|
||||||
|
if self.redeem_type is None:
|
||||||
|
self.select()
|
||||||
|
|
||||||
|
if self.redeem_type == 0:
|
||||||
|
# 一次性
|
||||||
|
self.c.execute(
|
||||||
|
'''select exists(select * from user_redeem where code=:a)''', {'a': self.code})
|
||||||
|
if self.c.fetchone() == (1,):
|
||||||
|
return False
|
||||||
|
elif self.redeem_type == 1:
|
||||||
|
# 每个玩家一次
|
||||||
|
self.c.execute('''select exists(select * from user_redeem where code=:a and user_id=:b)''',
|
||||||
|
{'a': self.code, 'b': self.user.user_id})
|
||||||
|
if self.c.fetchone() == (1,):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def insert_user_redeem(self) -> None:
|
||||||
|
self.c.execute('''insert into user_redeem values(:b,:a)''',
|
||||||
|
{'a': self.code, 'b': self.user.user_id})
|
||||||
|
|
||||||
|
def claim_user_redeem(self, code: str = None) -> None:
|
||||||
|
if code:
|
||||||
|
self.code = code
|
||||||
|
if not self.is_available:
|
||||||
|
if self.redeem_type == 0:
|
||||||
|
raise RedeemUnavailable(
|
||||||
|
'The redeem `%s` is unavailable.' % self.code)
|
||||||
|
elif self.redeem_type == 1:
|
||||||
|
raise RedeemUnavailable(
|
||||||
|
'The redeem `%s` is unavailable.' % self.code, 506)
|
||||||
|
|
||||||
|
if not self.items:
|
||||||
|
self.select_items()
|
||||||
|
|
||||||
|
self.insert_user_redeem()
|
||||||
|
|
||||||
|
self.fragment = 0
|
||||||
|
for i in self.items:
|
||||||
|
if i.item_type == 'fragment':
|
||||||
|
self.fragment += i.amount
|
||||||
|
else:
|
||||||
|
i.user_claim_item(self.user)
|
||||||
123
latest version/core/save.py
Normal file
123
latest version/core/save.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import json
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from core.constant import Constant
|
||||||
|
|
||||||
|
from .error import InputError
|
||||||
|
from .util import md5
|
||||||
|
|
||||||
|
|
||||||
|
class SaveData:
|
||||||
|
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.scores_data = []
|
||||||
|
self.clearlamps_data = []
|
||||||
|
self.clearedsongs_data = []
|
||||||
|
self.unlocklist_data = []
|
||||||
|
self.installid_data: str = ''
|
||||||
|
self.devicemodelname_data: str = ''
|
||||||
|
self.story_data = []
|
||||||
|
self.createdAt: int = 0
|
||||||
|
self.finalestate_data: str = ''
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"user_id": self.user.user_id,
|
||||||
|
"story": {
|
||||||
|
"": self.story_data
|
||||||
|
},
|
||||||
|
"devicemodelname": {
|
||||||
|
"val": self.devicemodelname_data
|
||||||
|
},
|
||||||
|
"installid": {
|
||||||
|
"val": self.installid_data
|
||||||
|
},
|
||||||
|
"unlocklist": {
|
||||||
|
"": self.unlocklist_data
|
||||||
|
},
|
||||||
|
"clearedsongs": {
|
||||||
|
"": self.clearedsongs_data
|
||||||
|
},
|
||||||
|
"clearlamps": {
|
||||||
|
"": self.clearlamps_data
|
||||||
|
},
|
||||||
|
"scores": {
|
||||||
|
"": self.scores_data
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"val": 1
|
||||||
|
},
|
||||||
|
"createdAt": self.createdAt,
|
||||||
|
'finalestate': {
|
||||||
|
'val': self.finalestate_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def select_all(self, user) -> None:
|
||||||
|
'''
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
self.user = user
|
||||||
|
self.c.execute('''select * from user_save where user_id=:a''',
|
||||||
|
{'a': user.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.scores_data = json.loads(x[1])[""]
|
||||||
|
self.clearlamps_data = json.loads(x[2])[""]
|
||||||
|
self.clearedsongs_data = json.loads(x[3])[""]
|
||||||
|
self.unlocklist_data = json.loads(x[4])[""]
|
||||||
|
self.installid_data = json.loads(x[5])["val"]
|
||||||
|
self.devicemodelname_data = json.loads(x[6])["val"]
|
||||||
|
self.story_data = json.loads(x[7])[""]
|
||||||
|
if x[8] is not None:
|
||||||
|
self.createdAt = int(x[8])
|
||||||
|
self.finalestate_data = x[9] if x[9] is not None else ''
|
||||||
|
|
||||||
|
if Config.SAVE_FULL_UNLOCK:
|
||||||
|
self.installid_data = "0fcec8ed-7b62-48e2-9d61-55041a22b123" # 使得可以进入存档选择上传或下载界面
|
||||||
|
for i in self.story_data:
|
||||||
|
i['c'] = True
|
||||||
|
i['r'] = True
|
||||||
|
for i in self.unlocklist_data:
|
||||||
|
if i['unlock_key'][-3:] == '101':
|
||||||
|
i['complete'] = 100
|
||||||
|
elif i['unlock_key'][:16] == 'aegleseeker|2|3|':
|
||||||
|
i['complete'] = 10
|
||||||
|
elif i['unlock_key'] == 'saikyostronger|2|3|einherjar|2':
|
||||||
|
i['complete'] = 6
|
||||||
|
elif i['unlock_key'] == 'saikyostronger|2|3|laqryma|2':
|
||||||
|
i['complete'] = 3
|
||||||
|
else:
|
||||||
|
i['complete'] = 1
|
||||||
|
|
||||||
|
self.finalestate_data = '|'.join(['0', '100'] + [str(i[1]) for i in Constant.SHIT_DATA_OF_616] + [
|
||||||
|
str(i) for i in Constant.UNKNOWN_SHIT] + ['1337'])
|
||||||
|
|
||||||
|
def update_all(self, user) -> None:
|
||||||
|
'''
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
self.createdAt = int(time() * 1000)
|
||||||
|
self.c.execute('''delete from user_save where user_id=:a''', {
|
||||||
|
'a': user.user_id})
|
||||||
|
self.c.execute('''insert into user_save values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j)''', {
|
||||||
|
'a': user.user_id, 'b': json.dumps({'': self.scores_data}), 'c': json.dumps({'': self.clearlamps_data}), 'd': json.dumps({'': self.clearedsongs_data}), 'e': json.dumps({'': self.unlocklist_data}), 'f': json.dumps({'val': self.installid_data}), 'g': json.dumps({'val': self.devicemodelname_data}), 'h': json.dumps({'': self.story_data}), 'i': self.createdAt, 'j': self.finalestate_data})
|
||||||
|
|
||||||
|
def set_value(self, key: str, value: str, checksum: str) -> None:
|
||||||
|
'''
|
||||||
|
从Arcaea客户端给的奇怪字符串中获取存档数据,并进行数据校验
|
||||||
|
'''
|
||||||
|
if key not in self.__dict__:
|
||||||
|
raise KeyError(
|
||||||
|
'Property `%s` is not found in the instance of `SaveData` class.' % key)
|
||||||
|
|
||||||
|
if md5(value) == checksum:
|
||||||
|
if key == 'installid_data' or key == 'devicemodelname_data' or key == 'finalestate_data':
|
||||||
|
self.__dict__[key] = json.loads(value)['val']
|
||||||
|
else:
|
||||||
|
self.__dict__[key] = json.loads(value)['']
|
||||||
|
else:
|
||||||
|
raise InputError('Hash value of cloud save data mismatches.')
|
||||||
571
latest version/core/score.py
Normal file
571
latest version/core/score.py
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
from os import urandom
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
from .course import CoursePlay
|
||||||
|
from .error import NoData, StaminaNotEnough
|
||||||
|
from .item import ItemCore
|
||||||
|
from .song import Chart
|
||||||
|
from .sql import Query, Sql
|
||||||
|
from .util import md5
|
||||||
|
from .world import WorldPlay
|
||||||
|
|
||||||
|
|
||||||
|
class Score:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.song: 'Chart' = Chart()
|
||||||
|
self.score: int = None
|
||||||
|
self.shiny_perfect_count: int = None
|
||||||
|
self.perfect_count: int = None
|
||||||
|
self.near_count: int = None
|
||||||
|
self.miss_count: int = None
|
||||||
|
self.health: int = None
|
||||||
|
self.modifier: int = None
|
||||||
|
self.time_played: int = None
|
||||||
|
self.best_clear_type: int = None
|
||||||
|
self.clear_type: int = None
|
||||||
|
self.rating: float = None
|
||||||
|
|
||||||
|
def set_score(self, 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):
|
||||||
|
self.score = int(score) if score is not None else 0
|
||||||
|
self.shiny_perfect_count = int(
|
||||||
|
shiny_perfect_count) if shiny_perfect_count is not None else 0
|
||||||
|
self.perfect_count = int(
|
||||||
|
perfect_count) if perfect_count is not None else 0
|
||||||
|
self.near_count = int(near_count) if near_count is not None else 0
|
||||||
|
self.miss_count = int(miss_count) if miss_count is not None else 0
|
||||||
|
self.health = int(health) if health is not None else 0
|
||||||
|
self.modifier = int(modifier) if modifier is not None else 0
|
||||||
|
self.time_played = int(time_played) if time_played is not None else 0
|
||||||
|
self.clear_type = int(clear_type) if clear_type is not None else 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_song_grade(score: int) -> int:
|
||||||
|
'''分数转换为评级'''
|
||||||
|
if score >= 9900000: # EX+
|
||||||
|
return 6
|
||||||
|
elif 9800000 <= score < 9900000: # EX
|
||||||
|
return 5
|
||||||
|
elif 9500000 <= score < 9800000: # AA
|
||||||
|
return 4
|
||||||
|
elif 9200000 <= score < 9500000: # A
|
||||||
|
return 3
|
||||||
|
elif 8900000 <= score < 9200000: # B
|
||||||
|
return 2
|
||||||
|
elif 8600000 <= score < 8900000: # C
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def song_grade(self) -> int:
|
||||||
|
return self.get_song_grade(self.score)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_song_state(clear_type: int) -> int:
|
||||||
|
'''clear_type转换为成绩状态,用数字大小标识便于比较'''
|
||||||
|
if clear_type == 3: # PM
|
||||||
|
return 5
|
||||||
|
elif clear_type == 2: # FC
|
||||||
|
return 4
|
||||||
|
elif clear_type == 5: # Hard Clear
|
||||||
|
return 3
|
||||||
|
elif clear_type == 1: # Clear
|
||||||
|
return 2
|
||||||
|
elif clear_type == 4: # Easy Clear
|
||||||
|
return 1
|
||||||
|
else: # Track Lost
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def song_state(self) -> int:
|
||||||
|
return self.get_song_state(self.clear_type)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
'''分数有效性检查'''
|
||||||
|
if self.shiny_perfect_count < 0 or self.perfect_count < 0 or self.near_count < 0 or self.miss_count < 0 or self.score < 0 or self.time_played <= 0:
|
||||||
|
return False
|
||||||
|
if self.song.difficulty not in (0, 1, 2, 3):
|
||||||
|
return False
|
||||||
|
|
||||||
|
all_note = self.perfect_count + self.near_count + self.miss_count
|
||||||
|
if all_note == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
calc_score = 10000000 / all_note * \
|
||||||
|
(self.perfect_count + self.near_count/2) + self.shiny_perfect_count
|
||||||
|
if abs(calc_score - self.score) >= 5:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_rating(defnum: int, score: int) -> float:
|
||||||
|
'''计算rating,谱面定数小于等于0视为Unrank,这里的defnum = Chart const'''
|
||||||
|
if not defnum or defnum <= 0:
|
||||||
|
# 谱面没定数或者定数小于等于0被视作Unrank
|
||||||
|
return -1
|
||||||
|
|
||||||
|
if score >= 10000000:
|
||||||
|
ptt = defnum + 2
|
||||||
|
elif score < 9800000:
|
||||||
|
ptt = defnum + (score-9500000) / 300000
|
||||||
|
if ptt < 0:
|
||||||
|
ptt = 0
|
||||||
|
else:
|
||||||
|
ptt = defnum + 1 + (score-9800000) / 200000
|
||||||
|
|
||||||
|
return ptt
|
||||||
|
|
||||||
|
def get_rating_by_calc(self) -> float:
|
||||||
|
# 通过计算得到本成绩的rating
|
||||||
|
if not self.song.defnum:
|
||||||
|
self.song.c = self.c
|
||||||
|
self.song.select()
|
||||||
|
self.rating = self.calculate_rating(self.song.chart_const, self.score)
|
||||||
|
return self.rating
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"rating": self.rating,
|
||||||
|
"modifier": self.modifier,
|
||||||
|
"time_played": self.time_played,
|
||||||
|
"health": self.health,
|
||||||
|
"clear_type": self.clear_type,
|
||||||
|
"miss_count": self.miss_count,
|
||||||
|
"near_count": self.near_count,
|
||||||
|
"perfect_count": self.perfect_count,
|
||||||
|
"shiny_perfect_count": self.shiny_perfect_count,
|
||||||
|
"score": self.score,
|
||||||
|
"difficulty": self.song.difficulty,
|
||||||
|
"song_id": self.song.song_id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserScore(Score):
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
'''
|
||||||
|
parameter: `user` - `UserInfo`类或子类的实例
|
||||||
|
'''
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.rank = None
|
||||||
|
|
||||||
|
def select_score(self) -> None:
|
||||||
|
self.c.execute('''select * from best_score where user_id = :a and song_id = :b and difficulty = :c''',
|
||||||
|
{'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('No score data.')
|
||||||
|
self.user.select_user_about_character()
|
||||||
|
|
||||||
|
self.from_list(x)
|
||||||
|
|
||||||
|
def from_list(self, x: list) -> 'UserScore':
|
||||||
|
if self.song.song_id is None:
|
||||||
|
self.song.song_id = x[1]
|
||||||
|
if self.song.difficulty is None:
|
||||||
|
self.song.difficulty = x[2]
|
||||||
|
self.set_score(x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[12])
|
||||||
|
self.best_clear_type = int(x[11])
|
||||||
|
self.rating = float(x[13])
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def to_dict(self, has_user_info: bool = True) -> dict:
|
||||||
|
r = super().to_dict()
|
||||||
|
r['best_clear_type'] = self.best_clear_type
|
||||||
|
if has_user_info:
|
||||||
|
r['user_id'] = self.user.user_id
|
||||||
|
r['name'] = self.user.name
|
||||||
|
r['is_skill_sealed'] = self.user.is_skill_sealed
|
||||||
|
character = self.user.character_displayed
|
||||||
|
r['is_char_uncapped'] = character.is_uncapped_displayed
|
||||||
|
r['character'] = character.character_id
|
||||||
|
if self.rank:
|
||||||
|
r['rank'] = self.rank
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class UserPlay(UserScore):
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__(c, user)
|
||||||
|
self.song_token: str = None
|
||||||
|
self.song_hash: str = None
|
||||||
|
self.submission_hash: str = None
|
||||||
|
self.beyond_gauge: int = None
|
||||||
|
self.unrank_flag: bool = None
|
||||||
|
self.first_protect_flag: bool = None
|
||||||
|
self.ptt: 'Potential' = None
|
||||||
|
|
||||||
|
self.is_world_mode: bool = None
|
||||||
|
self.stamina_multiply: int = None
|
||||||
|
self.fragment_multiply: int = None
|
||||||
|
self.prog_boost_multiply: int = None
|
||||||
|
|
||||||
|
self.ptt: Potential = None # 临时用来计算用户ptt的
|
||||||
|
self.world_play: 'WorldPlay' = None
|
||||||
|
|
||||||
|
self.course_play_state: int = None
|
||||||
|
self.course_play: 'CoursePlay' = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
if self.is_world_mode is None or self.course_play_state is None:
|
||||||
|
return {}
|
||||||
|
if self.course_play_state == 4:
|
||||||
|
r = self.course_play.to_dict()
|
||||||
|
elif self.is_world_mode:
|
||||||
|
r = self.world_play.to_dict()
|
||||||
|
else:
|
||||||
|
r = {}
|
||||||
|
r['user_rating'] = self.user.rating_ptt
|
||||||
|
r['global_rank'] = self.user.global_rank
|
||||||
|
r['finale_play_value'] = 0 # emmmm
|
||||||
|
return r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_protected(self) -> bool:
|
||||||
|
return self.health == -1 or int(self.score) >= 9800000 or self.first_protect_flag
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
'''分数有效性检查,带hash校验'''
|
||||||
|
if not super().is_valid:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 歌曲谱面MD5检查,服务器没有谱面就不管了
|
||||||
|
from .download import get_song_file_md5
|
||||||
|
songfile_hash = get_song_file_md5(
|
||||||
|
self.song.song_id, str(self.song.difficulty) + '.aff')
|
||||||
|
if songfile_hash and songfile_hash != self.song_hash:
|
||||||
|
return False
|
||||||
|
|
||||||
|
x = self.song_token + self.song_hash + self.song.song_id + str(self.song.difficulty) + str(self.score) + str(self.shiny_perfect_count) + str(
|
||||||
|
self.perfect_count) + str(self.near_count) + str(self.miss_count) + str(self.health) + str(self.modifier) + str(self.clear_type)
|
||||||
|
y = str(self.user.user_id) + self.song_hash
|
||||||
|
checksum = md5(x+md5(y))
|
||||||
|
if checksum != self.submission_hash:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_play_state(self) -> None:
|
||||||
|
'''检查token,当然这里不管有没有,是用来判断世界模式和课题模式的'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from songplay_token where token=:a ''', {'a': self.song_token})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
self.is_world_mode = False
|
||||||
|
self.course_play_state = -1
|
||||||
|
return None
|
||||||
|
self.song.set_chart(x[2], x[3])
|
||||||
|
if x[4]:
|
||||||
|
self.course_play = CoursePlay(self.c, self.user, self)
|
||||||
|
self.course_play.course_id = x[4]
|
||||||
|
self.course_play.score = x[6]
|
||||||
|
self.course_play.clear_type = x[7]
|
||||||
|
self.is_world_mode = False
|
||||||
|
self.course_play_state = x[5]
|
||||||
|
else:
|
||||||
|
self.stamina_multiply = int(x[8])
|
||||||
|
self.fragment_multiply = int(x[9])
|
||||||
|
self.prog_boost_multiply = int(x[10])
|
||||||
|
self.is_world_mode = True
|
||||||
|
self.course_play_state = -1
|
||||||
|
|
||||||
|
def set_play_state_for_world(self, stamina_multiply: int = 1, fragment_multiply: int = 100, prog_boost_multiply: int = 0) -> None:
|
||||||
|
self.song_token = b64encode(urandom(64)).decode()
|
||||||
|
self.stamina_multiply = int(stamina_multiply)
|
||||||
|
self.fragment_multiply = int(fragment_multiply)
|
||||||
|
self.prog_boost_multiply = int(prog_boost_multiply)
|
||||||
|
if self.prog_boost_multiply != 0:
|
||||||
|
self.c.execute('''select prog_boost from user where user_id=:a''', {
|
||||||
|
'a': self.user.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x and x[0] == 1:
|
||||||
|
self.prog_boost_multiply = 300
|
||||||
|
|
||||||
|
self.clear_play_state()
|
||||||
|
self.c.execute('''insert into songplay_token values(:t,:a,:b,:c,'',-1,0,0,:d,:e,:f)''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.stamina_multiply, 'e': self.fragment_multiply, 'f': self.prog_boost_multiply, 't': self.song_token})
|
||||||
|
|
||||||
|
self.user.select_user_about_current_map()
|
||||||
|
self.user.current_map.select_map_info()
|
||||||
|
|
||||||
|
self.user.select_user_about_stamina()
|
||||||
|
if self.user.stamina.stamina < self.user.current_map.stamina_cost * self.stamina_multiply:
|
||||||
|
raise StaminaNotEnough('Stamina is not enough.')
|
||||||
|
|
||||||
|
self.user.select_user_about_character()
|
||||||
|
if not self.user.is_skill_sealed:
|
||||||
|
self.user.character.select_character_info()
|
||||||
|
if self.user.character.skill_id_displayed == 'skill_fatalis':
|
||||||
|
# 特殊判断hikari fatalis的双倍体力消耗
|
||||||
|
self.user.stamina.stamina -= self.user.current_map.stamina_cost * \
|
||||||
|
self.stamina_multiply * 2
|
||||||
|
self.user.stamina.update()
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.user.stamina.stamina -= self.user.current_map.stamina_cost * self.stamina_multiply
|
||||||
|
self.user.stamina.update()
|
||||||
|
|
||||||
|
def set_play_state_for_course(self, use_course_skip_purchase: bool, course_id: str = None) -> None:
|
||||||
|
'''课题模式打歌初始化'''
|
||||||
|
self.song_token = 'c_' + b64encode(urandom(64)).decode()
|
||||||
|
if course_id is not None:
|
||||||
|
self.course_play.course_id = course_id
|
||||||
|
|
||||||
|
self.course_play_state = 0
|
||||||
|
self.course_play.score = 0
|
||||||
|
self.course_play.clear_type = 3 # 设置为PM,即最大值
|
||||||
|
|
||||||
|
self.c.execute('''insert into songplay_token values(?,?,?,?,?,?,?,?,1,100,0)''', (self.song_token, self.user.user_id, self.song.song_id,
|
||||||
|
self.song.difficulty, self.course_play.course_id, self.course_play_state, self.course_play.score, self.course_play.clear_type))
|
||||||
|
self.user.select_user_about_stamina()
|
||||||
|
if use_course_skip_purchase:
|
||||||
|
x = ItemCore(self.c)
|
||||||
|
x.item_id = 'core_course_skip_purchase'
|
||||||
|
x.amount = -1
|
||||||
|
x.user_claim_item(self.user)
|
||||||
|
else:
|
||||||
|
if self.user.stamina.stamina < Constant.COURSE_STAMINA_COST:
|
||||||
|
raise StaminaNotEnough('Stamina is not enough.')
|
||||||
|
self.user.stamina.stamina -= Constant.COURSE_STAMINA_COST
|
||||||
|
self.user.stamina.update()
|
||||||
|
|
||||||
|
def update_token_for_course(self) -> None:
|
||||||
|
'''课题模式更新token,并查用户体力'''
|
||||||
|
previous_token = self.song_token
|
||||||
|
self.song_token = 'c_' + b64encode(urandom(64)).decode()
|
||||||
|
self.c.execute('''update songplay_token set token=? where token=?''',
|
||||||
|
(self.song_token, previous_token))
|
||||||
|
self.user.select_user_about_stamina()
|
||||||
|
|
||||||
|
def update_play_state_for_course(self) -> None:
|
||||||
|
self.c.execute('''update songplay_token set course_state=?, course_score=?, course_clear_type=? where token=?''',
|
||||||
|
(self.course_play_state, self.course_play.score, self.course_play.clear_type, self.song_token))
|
||||||
|
|
||||||
|
def clear_play_state(self) -> None:
|
||||||
|
self.c.execute('''delete from songplay_token where user_id=:a ''', {
|
||||||
|
'a': self.user.user_id})
|
||||||
|
|
||||||
|
def update_recent30(self) -> None:
|
||||||
|
'''更新此分数对应用户的recent30'''
|
||||||
|
old_recent_10 = self.ptt.recent_10
|
||||||
|
if self.is_protected:
|
||||||
|
old_r30 = [x for x in self.ptt.r30]
|
||||||
|
old_s30 = [x for x in self.ptt.s30]
|
||||||
|
|
||||||
|
# 寻找pop位置
|
||||||
|
songs = list(set(self.ptt.s30))
|
||||||
|
if '' in self.ptt.s30:
|
||||||
|
r30_id = 29
|
||||||
|
else:
|
||||||
|
n = len(songs)
|
||||||
|
if n >= 11:
|
||||||
|
r30_id = 29
|
||||||
|
elif self.song.song_id_difficulty not in songs and n == 10:
|
||||||
|
r30_id = 29
|
||||||
|
elif self.song.song_id_difficulty in songs and n == 10:
|
||||||
|
i = 29
|
||||||
|
while self.ptt.s30[i] == self.song.song_id_difficulty:
|
||||||
|
i -= 1
|
||||||
|
r30_id = i
|
||||||
|
elif self.song.song_id_difficulty not in songs and n == 9:
|
||||||
|
i = 29
|
||||||
|
while self.ptt.s30.count(self.ptt.s30[-1]) == 1:
|
||||||
|
i -= 1
|
||||||
|
r30_id = i
|
||||||
|
else:
|
||||||
|
r30_id = 29
|
||||||
|
|
||||||
|
self.ptt.recent_30_update(
|
||||||
|
r30_id, self.rating, self.song.song_id_difficulty)
|
||||||
|
if self.is_protected and old_recent_10 > self.ptt.recent_10:
|
||||||
|
if self.song.song_id_difficulty in old_s30:
|
||||||
|
# 发现重复歌曲,更新到最高rating
|
||||||
|
index = old_s30.index(self.song.song_id_difficulty)
|
||||||
|
if old_r30[index] < self.rating:
|
||||||
|
old_r30[index] = self.rating
|
||||||
|
|
||||||
|
self.ptt.r30 = old_r30
|
||||||
|
self.ptt.s30 = old_s30
|
||||||
|
|
||||||
|
self.ptt.insert_recent_30()
|
||||||
|
|
||||||
|
def upload_score(self) -> None:
|
||||||
|
'''上传分数,包括user的recent更新,best更新,recent30更新,世界模式计算'''
|
||||||
|
self.get_play_state()
|
||||||
|
self.get_rating_by_calc()
|
||||||
|
if self.rating < 0:
|
||||||
|
self.unrank_flag = True
|
||||||
|
self.rating = 0
|
||||||
|
else:
|
||||||
|
self.unrank_flag = False
|
||||||
|
|
||||||
|
self.time_played = int(time())
|
||||||
|
|
||||||
|
# recent更新
|
||||||
|
self.c.execute('''update user set song_id = :b, difficulty = :c, score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.score, 'e': self.shiny_perfect_count, 'f': self.perfect_count, 'g': self.near_count, 'h': self.miss_count, 'i': self.health, 'j': self.modifier, 'k': self.clear_type, 'l': self.rating, 'm': self.time_played * 1000})
|
||||||
|
|
||||||
|
# 成绩录入
|
||||||
|
self.c.execute('''select score, best_clear_type from best_score where user_id = :a and song_id = :b and difficulty = :c''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
self.first_protect_flag = True # 初见保护
|
||||||
|
self.c.execute('''insert into best_score values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n)''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.score, 'e': self.shiny_perfect_count, 'f': self.perfect_count, 'g': self.near_count, 'h': self.miss_count, 'i': self.health, 'j': self.modifier, 'k': self.time_played, 'l': self.clear_type, 'm': self.clear_type, 'n': self.rating})
|
||||||
|
self.user.update_global_rank()
|
||||||
|
else:
|
||||||
|
self.first_protect_flag = False
|
||||||
|
if self.song_state > self.get_song_state(int(x[1])): # best状态更新
|
||||||
|
self.c.execute('''update best_score set best_clear_type = :a where user_id = :b and song_id = :c and difficulty = :d''', {
|
||||||
|
'a': self.clear_type, 'b': self.user.user_id, 'c': self.song.song_id, 'd': self.song.difficulty})
|
||||||
|
if self.score >= int(x[0]): # best成绩更新
|
||||||
|
self.c.execute('''update best_score set score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a and song_id = :b and difficulty = :c ''', {
|
||||||
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.score, 'e': self.shiny_perfect_count, 'f': self.perfect_count, 'g': self.near_count, 'h': self.miss_count, 'i': self.health, 'j': self.modifier, 'k': self.clear_type, 'l': self.rating, 'm': self.time_played})
|
||||||
|
self.user.update_global_rank()
|
||||||
|
|
||||||
|
self.ptt = Potential(self.c, self.user)
|
||||||
|
if not self.unrank_flag:
|
||||||
|
self.update_recent30()
|
||||||
|
|
||||||
|
# 总PTT更新
|
||||||
|
self.user.rating_ptt = int(self.ptt.value * 100)
|
||||||
|
self.c.execute('''update user set rating_ptt = :a where user_id = :b''', {
|
||||||
|
'a': self.user.rating_ptt, 'b': self.user.user_id})
|
||||||
|
|
||||||
|
# 世界模式判断
|
||||||
|
if self.is_world_mode:
|
||||||
|
self.world_play = WorldPlay(self.c, self.user, self)
|
||||||
|
self.world_play.update()
|
||||||
|
|
||||||
|
# 课题模式判断
|
||||||
|
if self.course_play_state >= 0:
|
||||||
|
self.course_play.update()
|
||||||
|
|
||||||
|
|
||||||
|
class Potential:
|
||||||
|
'''
|
||||||
|
用户潜力值计算处理类\
|
||||||
|
property: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.r30: list = None
|
||||||
|
self.s30: list = None
|
||||||
|
self.songs_selected: list = None
|
||||||
|
|
||||||
|
self.b30: list = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> float:
|
||||||
|
'''计算用户潜力值'''
|
||||||
|
return self.best_30 * Constant.BEST30_WEIGHT + self.recent_10 * Constant.RECENT10_WEIGHT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def best_30(self) -> float:
|
||||||
|
'''获取用户best30的总潜力值'''
|
||||||
|
self.c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', {
|
||||||
|
'a': self.user.user_id})
|
||||||
|
return sum([x[0] for x in self.c.fetchall()])
|
||||||
|
|
||||||
|
def select_recent_30(self) -> None:
|
||||||
|
'''获取用户recent30数据'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from recent30 where user_id = :a''', {'a': self.user.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
self.r30 = []
|
||||||
|
self.s30 = []
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
for i in range(1, 61, 2):
|
||||||
|
if x[i] is not None:
|
||||||
|
self.r30.append(float(x[i]))
|
||||||
|
self.s30.append(x[i+1])
|
||||||
|
else:
|
||||||
|
self.r30.append(0)
|
||||||
|
self.s30.append('')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recent_10(self) -> float:
|
||||||
|
'''获取用户recent10的总潜力值'''
|
||||||
|
if self.r30 is None:
|
||||||
|
self.select_recent_30()
|
||||||
|
|
||||||
|
rating_sum = 0
|
||||||
|
r30, s30 = (list(t) for t in zip(
|
||||||
|
*sorted(zip(self.r30, self.s30), reverse=True)))
|
||||||
|
|
||||||
|
self.songs_selected = []
|
||||||
|
i = 0
|
||||||
|
while len(self.songs_selected) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None:
|
||||||
|
if s30[i] not in self.songs_selected:
|
||||||
|
rating_sum += r30[i]
|
||||||
|
self.songs_selected.append(s30[i])
|
||||||
|
i += 1
|
||||||
|
return rating_sum
|
||||||
|
|
||||||
|
def recent_30_to_dict_list(self) -> list:
|
||||||
|
if self.r30 is None:
|
||||||
|
self.select_recent_30()
|
||||||
|
return [{
|
||||||
|
'song_id': self.s30[i][:-1],
|
||||||
|
'difficulty': int(self.s30[i][-1]),
|
||||||
|
'rating': self.r30[i]
|
||||||
|
} for i in range(len(self.r30))]
|
||||||
|
|
||||||
|
def recent_30_update(self, pop_index: int, rating: float, song_id_difficulty: str) -> None:
|
||||||
|
self.r30.pop(pop_index)
|
||||||
|
self.s30.pop(pop_index)
|
||||||
|
self.r30.insert(0, rating)
|
||||||
|
self.s30.insert(0, song_id_difficulty)
|
||||||
|
|
||||||
|
def insert_recent_30(self) -> None:
|
||||||
|
'''更新r30表数据'''
|
||||||
|
sql = '''update recent30 set r0=?,song_id0=?,r1=?,song_id1=?,r2=?,song_id2=?,r3=?,song_id3=?,r4=?,song_id4=?,r5=?,song_id5=?,r6=?,song_id6=?,r7=?,song_id7=?,r8=?,song_id8=?,r9=?,song_id9=?,r10=?,song_id10=?,r11=?,song_id11=?,r12=?,song_id12=?,r13=?,song_id13=?,r14=?,song_id14=?,r15=?,song_id15=?,r16=?,song_id16=?,r17=?,song_id17=?,r18=?,song_id18=?,r19=?,song_id19=?,r20=?,song_id20=?,r21=?,song_id21=?,r22=?,song_id22=?,r23=?,song_id23=?,r24=?,song_id24=?,r25=?,song_id25=?,r26=?,song_id26=?,r27=?,song_id27=?,r28=?,song_id28=?,r29=?,song_id29=? where user_id=?'''
|
||||||
|
sql_list = []
|
||||||
|
for i in range(30):
|
||||||
|
sql_list.append(self.r30[i])
|
||||||
|
sql_list.append(self.s30[i])
|
||||||
|
|
||||||
|
sql_list.append(self.user.user_id)
|
||||||
|
|
||||||
|
self.c.execute(sql, sql_list)
|
||||||
|
|
||||||
|
|
||||||
|
class UserScoreList:
|
||||||
|
'''
|
||||||
|
用户分数查询类\
|
||||||
|
properties: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None):
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.scores: list = None
|
||||||
|
self.query: 'Query' = Query(['user_id', 'song_id', 'difficulty'], ['song_id'], [
|
||||||
|
'rating', 'difficulty', 'song_id', 'score', 'time_played'])
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list:
|
||||||
|
return [x.to_dict(has_user_info=False) for x in self.scores]
|
||||||
|
|
||||||
|
def select_from_user(self, user=None) -> None:
|
||||||
|
'''获取用户的best_score数据'''
|
||||||
|
if user is not None:
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
self.query.query_append({'user_id': self.user.user_id})
|
||||||
|
self.query.sort += [{'column': 'rating', 'order': 'DESC'}]
|
||||||
|
print(self.query.sort)
|
||||||
|
x = Sql(self.c).select('best_score', query=self.query)
|
||||||
|
|
||||||
|
self.scores = [UserScore(self.c, self.user).from_list(i) for i in x]
|
||||||
81
latest version/core/song.py
Normal file
81
latest version/core/song.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from .error import NoData
|
||||||
|
|
||||||
|
|
||||||
|
class Chart:
|
||||||
|
# defnum: chart const * 10
|
||||||
|
|
||||||
|
def __init__(self, c=None, song_id: str = None, difficulty: int = None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.set_chart(song_id, difficulty)
|
||||||
|
self.defnum: int = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'difficulty': self.difficulty,
|
||||||
|
'chart_const': self.chart_const
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chart_const(self) -> float:
|
||||||
|
return self.defnum / 10 if self.defnum else -1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def song_id_difficulty(self) -> str:
|
||||||
|
return '%s%d' % (self.song_id, self.difficulty)
|
||||||
|
|
||||||
|
def set_chart(self, song_id: str = None, difficulty: int = None) -> None:
|
||||||
|
self.song_id = song_id
|
||||||
|
self.difficulty = int(difficulty) if difficulty is not None else None
|
||||||
|
|
||||||
|
def select(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''select rating_pst, rating_prs, rating_ftr, rating_byn from chart where song_id=:a''', {'a': self.song_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
self.defnum = -10
|
||||||
|
# raise NoData('The song `%s` does not exist.' % self.song_id)
|
||||||
|
else:
|
||||||
|
self.defnum = x[self.difficulty]
|
||||||
|
|
||||||
|
|
||||||
|
class Song:
|
||||||
|
def __init__(self, c=None, song_id=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.song_id: str = song_id
|
||||||
|
self.name: str = None
|
||||||
|
self.charts: dict = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'song_id': self.song_id,
|
||||||
|
'name': self.name,
|
||||||
|
'charts': [chart.to_dict() for chart in self.charts]
|
||||||
|
}
|
||||||
|
|
||||||
|
def from_list(self, x: list) -> 'Song':
|
||||||
|
if self.song_id is None:
|
||||||
|
self.song_id = x[0]
|
||||||
|
self.name = x[1]
|
||||||
|
self.charts = [Chart(self.c, self.song_id, 0), Chart(self.c, self.song_id, 1), Chart(
|
||||||
|
self.c, self.song_id, 2), Chart(self.c, self.song_id, 3)]
|
||||||
|
self.charts[0].defnum = x[2]
|
||||||
|
self.charts[1].defnum = x[3]
|
||||||
|
self.charts[2].defnum = x[4]
|
||||||
|
self.charts[3].defnum = x[5]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def insert(self) -> None:
|
||||||
|
self.c.execute(
|
||||||
|
'''insert into chart values (?,?,?,?,?,?)''', (self.song_id, self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum))
|
||||||
|
|
||||||
|
def select(self, song_id: str = None) -> 'Song':
|
||||||
|
if song_id is not None:
|
||||||
|
self.song_id = song_id
|
||||||
|
|
||||||
|
self.c.execute('''select * from chart where song_id=:a''', {
|
||||||
|
'a': self.song_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('The song `%s` does not exist.' % self.song_id)
|
||||||
|
|
||||||
|
return self.from_list(x)
|
||||||
216
latest version/core/sql.py
Normal file
216
latest version/core/sql.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import sqlite3
|
||||||
|
import traceback
|
||||||
|
from certifi import where
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
from .error import InputError
|
||||||
|
|
||||||
|
|
||||||
|
class Connect:
|
||||||
|
# 数据库连接类,上下文管理
|
||||||
|
|
||||||
|
def __init__(self, file_path=Constant.SQLITE_DATABASE_PATH):
|
||||||
|
"""
|
||||||
|
数据库连接,默认连接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) -> bool:
|
||||||
|
if exc_type is not None:
|
||||||
|
if self.conn:
|
||||||
|
self.conn.rollback()
|
||||||
|
|
||||||
|
current_app.logger.error(
|
||||||
|
traceback.format_exception(exc_type, exc_val, exc_tb))
|
||||||
|
|
||||||
|
if self.conn:
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Query:
|
||||||
|
'''查询参数类'''
|
||||||
|
|
||||||
|
def __init__(self, query_able: list = None, quzzy_query_able: list = None, sort_able: list = None) -> None:
|
||||||
|
self.query_able: list = query_able
|
||||||
|
self.quzzy_query_able: list = quzzy_query_able
|
||||||
|
self.sort_able: list = sort_able
|
||||||
|
|
||||||
|
self.__limit: int = -1
|
||||||
|
self.__offset: int = 0
|
||||||
|
self.__query: dict = {} # {'name': 'admin'}
|
||||||
|
self.__fuzzy_query: dict = {} # {'name': 'dmi'}
|
||||||
|
# [{'column': 'user_id', 'order': 'ASC'}, ...]
|
||||||
|
self.__sort: list = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def limit(self) -> int:
|
||||||
|
return self.__limit
|
||||||
|
|
||||||
|
@limit.setter
|
||||||
|
def limit(self, limit: int) -> None:
|
||||||
|
if not isinstance(limit, int):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
self.__limit = limit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def offset(self) -> int:
|
||||||
|
return self.__offset
|
||||||
|
|
||||||
|
@offset.setter
|
||||||
|
def offset(self, offset: int) -> None:
|
||||||
|
if not isinstance(offset, int):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
self.__offset = offset
|
||||||
|
|
||||||
|
@property
|
||||||
|
def query(self) -> dict:
|
||||||
|
return self.__query
|
||||||
|
|
||||||
|
@query.setter
|
||||||
|
def query(self, query: dict) -> None:
|
||||||
|
self.__query = {}
|
||||||
|
self.query_append(query)
|
||||||
|
|
||||||
|
def query_append(self, query: dict) -> None:
|
||||||
|
if not isinstance(query, dict):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
if self.query_able is not None and query and not set(query).issubset(set(self.query_able)):
|
||||||
|
raise InputError(api_error_code=-102)
|
||||||
|
if not self.__query:
|
||||||
|
self.__query = query
|
||||||
|
else:
|
||||||
|
self.__query.update(query)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fuzzy_query(self) -> dict:
|
||||||
|
return self.__fuzzy_query
|
||||||
|
|
||||||
|
@fuzzy_query.setter
|
||||||
|
def fuzzy_query(self, fuzzy_query: dict) -> None:
|
||||||
|
self.__fuzzy_query = {}
|
||||||
|
self.fuzzy_query_append(fuzzy_query)
|
||||||
|
|
||||||
|
def fuzzy_query_append(self, fuzzy_query: dict) -> None:
|
||||||
|
if not isinstance(fuzzy_query, dict):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
if self.quzzy_query_able is not None and fuzzy_query and not set(fuzzy_query).issubset(set(self.quzzy_query_able)):
|
||||||
|
raise InputError(api_error_code=-102)
|
||||||
|
if not self.__fuzzy_query:
|
||||||
|
self.__fuzzy_query = fuzzy_query
|
||||||
|
else:
|
||||||
|
self.__fuzzy_query.update(fuzzy_query)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sort(self) -> list:
|
||||||
|
return self.__sort
|
||||||
|
|
||||||
|
@sort.setter
|
||||||
|
def sort(self, sort: list) -> None:
|
||||||
|
if not isinstance(sort, list):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
if self.sort_able is not None and sort:
|
||||||
|
for x in sort:
|
||||||
|
if not isinstance(x, dict):
|
||||||
|
raise InputError(api_error_code=-101)
|
||||||
|
if 'column' not in x or x['column'] not in self.sort_able:
|
||||||
|
raise InputError(api_error_code=-103)
|
||||||
|
if 'order' not in x:
|
||||||
|
x['order'] = 'ASC'
|
||||||
|
else:
|
||||||
|
if x['order'] not in ['ASC', 'DESC']:
|
||||||
|
raise InputError(api_error_code=-104)
|
||||||
|
self.__sort = sort
|
||||||
|
|
||||||
|
def set_value(self, limit=-1, offset=0, query={}, fuzzy_query={}, sort=[]) -> None:
|
||||||
|
self.limit = limit
|
||||||
|
self.offset = offset
|
||||||
|
self.query = query
|
||||||
|
self.fuzzy_query = fuzzy_query
|
||||||
|
self.sort = sort
|
||||||
|
|
||||||
|
def from_data(self, d: dict) -> 'Query':
|
||||||
|
self.set_value(d.get('limit', -1), d.get('offset', 0),
|
||||||
|
d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', []))
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class Sql:
|
||||||
|
'''
|
||||||
|
数据库增查删改类
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
def select(self, table_name: str, target_column: 'list' = [], query: 'Query' = None) -> list:
|
||||||
|
'''单表内行查询单句sql语句,返回fetchall数据'''
|
||||||
|
|
||||||
|
sql = 'select '
|
||||||
|
sql_list = []
|
||||||
|
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
|
||||||
|
|
||||||
|
if query is None:
|
||||||
|
self.c.execute(sql)
|
||||||
|
return self.c.fetchall()
|
||||||
|
|
||||||
|
where_key = []
|
||||||
|
where_like_key = []
|
||||||
|
for i in query.query:
|
||||||
|
where_key.append(i)
|
||||||
|
sql_list.append(query.query[i])
|
||||||
|
|
||||||
|
for i in query.fuzzy_query:
|
||||||
|
where_like_key.append(i)
|
||||||
|
sql_list.append('%' + query.fuzzy_query[i] + '%')
|
||||||
|
|
||||||
|
if where_key or where_like_key:
|
||||||
|
sql += ' where '
|
||||||
|
if where_key:
|
||||||
|
sql += where_key[0] + '=?'
|
||||||
|
if len(where_key) >= 2:
|
||||||
|
for i in range(1, len(where_key)):
|
||||||
|
sql += ' and ' + where_key[i] + '=?'
|
||||||
|
if where_like_key:
|
||||||
|
for i in where_like_key:
|
||||||
|
sql += ' and ' + i + ' like ?'
|
||||||
|
else:
|
||||||
|
sql += where_like_key[0] + ' like ?'
|
||||||
|
|
||||||
|
if len(where_like_key) >= 2:
|
||||||
|
for i in range(1, len(where_key)):
|
||||||
|
sql += ' and ' + where_key[i] + ' like ?'
|
||||||
|
|
||||||
|
if 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.limit >= 0:
|
||||||
|
sql += ' limit ? offset ?'
|
||||||
|
sql_list.append(query.limit)
|
||||||
|
sql_list.append(query.offset)
|
||||||
|
|
||||||
|
self.c.execute(sql, sql_list)
|
||||||
|
return self.c.fetchall()
|
||||||
19
latest version/core/system.py
Normal file
19
latest version/core/system.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
|
||||||
|
|
||||||
|
class GameInfo:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"max_stamina": Constant.MAX_STAMINA,
|
||||||
|
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
|
||||||
|
"core_exp": Constant.CORE_EXP,
|
||||||
|
"curr_ts": int(time()*1000),
|
||||||
|
"level_steps": [{'level': i, 'level_exp': Constant.LEVEL_STEPS[i]} for i in Constant.LEVEL_STEPS],
|
||||||
|
"world_ranking_enabled": True,
|
||||||
|
"is_byd_chapter_unlocked": True
|
||||||
|
}
|
||||||
721
latest version/core/user.py
Normal file
721
latest version/core/user.py
Normal file
@@ -0,0 +1,721 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
from os import urandom
|
||||||
|
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from .character import UserCharacter, UserCharacterList
|
||||||
|
from .constant import Constant
|
||||||
|
from .error import (ArcError, DataExist, FriendError, InputError, NoAccess,
|
||||||
|
NoData, UserBan)
|
||||||
|
from .item import UserItemList
|
||||||
|
from .score import Score
|
||||||
|
from .sql import Connect
|
||||||
|
from .world import Map, UserMap, UserStamina
|
||||||
|
|
||||||
|
|
||||||
|
def code_get_id(c, user_code: str) -> int:
|
||||||
|
# 用user_code获取user_id
|
||||||
|
|
||||||
|
c.execute('''select user_id from user where user_code = :a''',
|
||||||
|
{'a': user_code})
|
||||||
|
x = c.fetchone()
|
||||||
|
|
||||||
|
if x is not None:
|
||||||
|
user_id = int(x[0])
|
||||||
|
else:
|
||||||
|
raise NoData('No user.', 401, -3)
|
||||||
|
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.name = None
|
||||||
|
self.email = None
|
||||||
|
self.password = None
|
||||||
|
self.user_id = None
|
||||||
|
self.user_code = None
|
||||||
|
|
||||||
|
self.join_date = None
|
||||||
|
self.rating_ptt: int = None # 100 times
|
||||||
|
|
||||||
|
self.ticket = None
|
||||||
|
self.world_rank_score = None
|
||||||
|
self.ban_flag = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hash_pwd(self) -> str:
|
||||||
|
'''`password`的SHA-256值'''
|
||||||
|
return hashlib.sha256(self.password.encode("utf8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
class UserRegister(User):
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
|
||||||
|
def set_name(self, name: str):
|
||||||
|
if 3 <= len(name) <= 16:
|
||||||
|
self.c.execute(
|
||||||
|
'''select exists(select * from user where name = :name)''', {'name': name})
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
self.name = name
|
||||||
|
else:
|
||||||
|
raise DataExist('Username exists.', 101, -203)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise InputError('Username is invalid.')
|
||||||
|
|
||||||
|
def set_password(self, password: str):
|
||||||
|
if 8 <= len(password) <= 32:
|
||||||
|
self.password = password
|
||||||
|
else:
|
||||||
|
raise InputError('Password is invalid.')
|
||||||
|
|
||||||
|
def set_email(self, email: str):
|
||||||
|
# 邮箱格式懒得多判断
|
||||||
|
if 4 <= len(email) <= 32 and '@' in email and '.' in email:
|
||||||
|
self.c.execute(
|
||||||
|
'''select exists(select * from user where email = :email)''', {'email': email})
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
self.email = email
|
||||||
|
else:
|
||||||
|
raise DataExist('Email address exists.', 102, -204)
|
||||||
|
else:
|
||||||
|
raise InputError('Email address is invalid.')
|
||||||
|
|
||||||
|
def _build_user_code(self):
|
||||||
|
# 生成9位的user_code,用的自然是随机
|
||||||
|
from random import randint
|
||||||
|
random_times = 0
|
||||||
|
|
||||||
|
while random_times <= 1000:
|
||||||
|
random_times += 1
|
||||||
|
user_code = ''.join([str(randint(0, 9)) for _ in range(9)])
|
||||||
|
self.c.execute('''select exists(select * from user where user_code = :user_code)''',
|
||||||
|
{'user_code': user_code})
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
break
|
||||||
|
|
||||||
|
if random_times <= 1000:
|
||||||
|
self.user_code = user_code
|
||||||
|
else:
|
||||||
|
raise ArcError('No available user code.')
|
||||||
|
|
||||||
|
def _build_user_id(self):
|
||||||
|
# 生成user_id,往后加1
|
||||||
|
self.c.execute('''select max(user_id) from user''')
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x[0] is not None:
|
||||||
|
self.user_id = x[0] + 1
|
||||||
|
else:
|
||||||
|
self.user_id = 2000001
|
||||||
|
|
||||||
|
def _insert_user_char(self):
|
||||||
|
# 为用户添加初始角色
|
||||||
|
self.c.execute('''insert into user_char values(?,?,?,?,?,?)''',
|
||||||
|
(self.user_id, 0, 1, 0, 0, 0))
|
||||||
|
self.c.execute('''insert into user_char values(?,?,?,?,?,?)''',
|
||||||
|
(self.user_id, 1, 1, 0, 0, 0))
|
||||||
|
self.c.execute(
|
||||||
|
'''select character_id, max_level, is_uncapped from character''')
|
||||||
|
x = self.c.fetchall()
|
||||||
|
if x:
|
||||||
|
for i in x:
|
||||||
|
exp = 25000 if i[1] == 30 else 10000
|
||||||
|
self.c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
|
||||||
|
(self.user_id, i[0], i[1], exp, i[2], 0))
|
||||||
|
|
||||||
|
def register(self):
|
||||||
|
now = int(time.time() * 1000)
|
||||||
|
self._build_user_code()
|
||||||
|
self._build_user_id()
|
||||||
|
self._insert_user_char()
|
||||||
|
|
||||||
|
self.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': self.user_code, 'user_id': self.user_id, 'join_date': now, 'name': self.name, 'password': self.hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': self.email})
|
||||||
|
self.c.execute('''insert into recent30(user_id) values(:user_id)''', {
|
||||||
|
'user_id': self.user_id})
|
||||||
|
|
||||||
|
|
||||||
|
class UserLogin(User):
|
||||||
|
# 密码和token的加密方式为 SHA-256
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.device_id = None
|
||||||
|
self.ip = None
|
||||||
|
self.token = None
|
||||||
|
self.now = 0
|
||||||
|
|
||||||
|
def set_name(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def set_password(self, password: str):
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def set_device_id(self, device_id: str):
|
||||||
|
self.device_id = device_id
|
||||||
|
|
||||||
|
def set_ip(self, ip: str):
|
||||||
|
self.ip = ip
|
||||||
|
|
||||||
|
def _arc_auto_ban(self):
|
||||||
|
# 多设备自动封号机制,返回封号时长
|
||||||
|
self.c.execute('''delete from login where user_id=?''',
|
||||||
|
(self.user_id, ))
|
||||||
|
self.c.execute(
|
||||||
|
'''select ban_flag from user where user_id=?''', (self.user_id,))
|
||||||
|
x = self.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(Constant.BAN_TIME) - 1 and Constant.BAN_TIME[i] <= last_ban_time:
|
||||||
|
i += 1
|
||||||
|
ban_time = Constant.BAN_TIME[i]
|
||||||
|
else:
|
||||||
|
ban_time = Constant.BAN_TIME[0]
|
||||||
|
|
||||||
|
ban_flag = ':'.join(
|
||||||
|
(str(ban_time), str(self.now + ban_time * 86400000)))
|
||||||
|
self.c.execute('''update user set ban_flag=? where user_id=?''',
|
||||||
|
(ban_flag, self.user_id))
|
||||||
|
|
||||||
|
return ban_time * 86400000
|
||||||
|
|
||||||
|
def _check_device(self, device_list):
|
||||||
|
should_delete_num = len(
|
||||||
|
device_list) + 1 - Config.LOGIN_DEVICE_NUMBER_LIMIT
|
||||||
|
|
||||||
|
if not Config.ALLOW_LOGIN_SAME_DEVICE:
|
||||||
|
if self.device_id in device_list: # 对相同设备进行删除
|
||||||
|
self.c.execute('''delete from login where login_device=:a and user_id=:b''', {
|
||||||
|
'a': self.device_id, 'b': self.user_id})
|
||||||
|
should_delete_num = len(
|
||||||
|
device_list) + 1 - device_list.count(self.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: # 自动封号检查
|
||||||
|
self.c.execute(
|
||||||
|
'''select count(*) from login where user_id=? and login_time>?''', (self.user_id, self.now-86400000))
|
||||||
|
if self.c.fetchone()[0] >= Config.LOGIN_DEVICE_NUMBER_LIMIT:
|
||||||
|
remaining_ts = self._arc_auto_ban()
|
||||||
|
raise UserBan('Too many devices logging in during 24 hours.', 105, extra_data={
|
||||||
|
'remaining_ts': remaining_ts})
|
||||||
|
|
||||||
|
self.c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''',
|
||||||
|
{'user_id': self.user_id, 'a': int(should_delete_num)})
|
||||||
|
|
||||||
|
def login(self, name: str = '', password: str = '', device_id: str = '', ip: str = ''):
|
||||||
|
if name:
|
||||||
|
self.set_name(name)
|
||||||
|
if password:
|
||||||
|
self.set_password(password)
|
||||||
|
if device_id:
|
||||||
|
self.set_device_id(device_id)
|
||||||
|
if ip:
|
||||||
|
self.set_ip(ip)
|
||||||
|
|
||||||
|
self.c.execute('''select user_id, password, ban_flag from user where name = :name''', {
|
||||||
|
'name': self.name})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is None:
|
||||||
|
raise NoData('Username does not exist.', 104)
|
||||||
|
|
||||||
|
self.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 > self.now:
|
||||||
|
raise UserBan('Too many devices logging in during 24 hours.', 105, extra_data={
|
||||||
|
'remaining_ts': ban_timestamp-self.now})
|
||||||
|
|
||||||
|
if x[1] == '':
|
||||||
|
# 账号封禁
|
||||||
|
raise UserBan('The account has been banned.', 106)
|
||||||
|
|
||||||
|
if x[1] != self.hash_pwd:
|
||||||
|
raise NoAccess('Wrong password.', 104)
|
||||||
|
|
||||||
|
self.user_id = str(x[0])
|
||||||
|
self.token = base64.b64encode(hashlib.sha256(
|
||||||
|
(self.user_id + str(self.now)).encode("utf8") + urandom(8)).digest()).decode()
|
||||||
|
|
||||||
|
self.c.execute(
|
||||||
|
'''select login_device from login where user_id = :user_id''', {"user_id": self.user_id})
|
||||||
|
y = self.c.fetchall()
|
||||||
|
if y:
|
||||||
|
self._check_device([i[0] if i[0] else '' for i in y])
|
||||||
|
|
||||||
|
self.c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
|
||||||
|
'user_id': self.user_id, 'access_token': self.token, 'device_id': self.device_id, 'time': self.now, 'ip': self.ip})
|
||||||
|
|
||||||
|
|
||||||
|
class UserAuth(User):
|
||||||
|
def __init__(self, c) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.token = None
|
||||||
|
|
||||||
|
def token_get_id(self):
|
||||||
|
# 用token获取id,没有考虑不同用户token相同情况,说不定会有bug
|
||||||
|
self.c.execute('''select user_id from login where access_token = :token''', {
|
||||||
|
'token': self.token})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x is not None:
|
||||||
|
self.user_id = x[0]
|
||||||
|
else:
|
||||||
|
raise NoAccess('Wrong token.', -4)
|
||||||
|
|
||||||
|
return self.user_id
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfo(User):
|
||||||
|
def __init__(self, c, user_id=None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.user_id = user_id
|
||||||
|
self.character = None
|
||||||
|
self.is_skill_sealed = False
|
||||||
|
self.is_hide_rating = False
|
||||||
|
self.recent_score = Score()
|
||||||
|
self.favorite_character = None
|
||||||
|
self.max_stamina_notification_enabled = False
|
||||||
|
self.prog_boost = 0
|
||||||
|
self.next_fragstam_ts: int = None
|
||||||
|
self.world_mode_locked_end_ts: int = None
|
||||||
|
|
||||||
|
self.__cores: list = None
|
||||||
|
self.__packs: list = None
|
||||||
|
self.__singles: list = None
|
||||||
|
self.characters: 'UserCharacterList' = None
|
||||||
|
self.__friends: list = None
|
||||||
|
self.__world_unlocks: list = None
|
||||||
|
self.__world_songs: list = None
|
||||||
|
self.curr_available_maps: list = None
|
||||||
|
self.__course_banners: list = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cores(self) -> list:
|
||||||
|
if self.__cores is None:
|
||||||
|
x = UserItemList(self.c, self.user_id).select_from_type('core')
|
||||||
|
self.__cores = [{'core_type': i.item_id,
|
||||||
|
'amount': i.amount} for i in x.items]
|
||||||
|
|
||||||
|
return self.__cores
|
||||||
|
|
||||||
|
@property
|
||||||
|
def singles(self) -> list:
|
||||||
|
if self.__singles is None:
|
||||||
|
x = UserItemList(self.c, self.user_id).select_from_type('single')
|
||||||
|
self.__singles = [i.item_id for i in x.items]
|
||||||
|
|
||||||
|
return self.__singles
|
||||||
|
|
||||||
|
@property
|
||||||
|
def packs(self) -> list:
|
||||||
|
if self.__packs is None:
|
||||||
|
x = UserItemList(self.c, self.user_id).select_from_type('pack')
|
||||||
|
self.__packs = [i.item_id for i in x.items]
|
||||||
|
|
||||||
|
return self.__packs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def world_unlocks(self) -> list:
|
||||||
|
if self.__world_unlocks is None:
|
||||||
|
x = UserItemList(self.c, self.user_id).select_from_type(
|
||||||
|
'world_unlock')
|
||||||
|
self.__world_unlocks = [i.item_id for i in x.items]
|
||||||
|
|
||||||
|
return self.__world_unlocks
|
||||||
|
|
||||||
|
@property
|
||||||
|
def world_songs(self) -> list:
|
||||||
|
if self.__world_songs is None:
|
||||||
|
x = UserItemList(
|
||||||
|
self.c, self.user_id).select_from_type('world_song')
|
||||||
|
self.__world_songs = [i.item_id for i in x.items]
|
||||||
|
|
||||||
|
return self.__world_songs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def course_banners(self) -> list:
|
||||||
|
if self.__course_banners is None:
|
||||||
|
x = UserItemList(
|
||||||
|
self.c, self.user_id).select_from_type('course_banner')
|
||||||
|
self.__course_banners = [i.item_id for i in x.items]
|
||||||
|
|
||||||
|
return self.__course_banners
|
||||||
|
|
||||||
|
def select_characters(self) -> None:
|
||||||
|
self.characters = UserCharacterList(self.c, self)
|
||||||
|
self.characters.select_user_characters()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def characters_list(self) -> list:
|
||||||
|
if self.characters is None:
|
||||||
|
self.select_characters()
|
||||||
|
return [x.character_id for x in self.characters.characters]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def character_displayed(self) -> 'UserCharacter':
|
||||||
|
'''对外显示的角色'''
|
||||||
|
if self.favorite_character is None:
|
||||||
|
return self.character
|
||||||
|
|
||||||
|
self.favorite_character.select_character_uncap_condition(self)
|
||||||
|
return self.favorite_character
|
||||||
|
|
||||||
|
@property
|
||||||
|
def friends(self) -> list:
|
||||||
|
# 得到用户的朋友列表
|
||||||
|
if self.__friends is None:
|
||||||
|
self.c.execute('''select user_id_other from friend where user_id_me = :user_id''', {
|
||||||
|
'user_id': self.user_id})
|
||||||
|
x = self.c.fetchall()
|
||||||
|
s = []
|
||||||
|
if x != [] and x[0][0] is not None:
|
||||||
|
for i in x:
|
||||||
|
self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
||||||
|
{'x': i[0], 'y': self.user_id})
|
||||||
|
|
||||||
|
is_mutual = True if self.c.fetchone() == (1,) else False
|
||||||
|
|
||||||
|
you = UserOnline(self.c, i[0])
|
||||||
|
you.select_user()
|
||||||
|
character = you.character if you.favorite_character is None else you.favorite_character
|
||||||
|
character.select_character_uncap_condition(you)
|
||||||
|
|
||||||
|
rating = you.rating_ptt if not you.is_hide_rating else -1
|
||||||
|
|
||||||
|
s.append({
|
||||||
|
"is_mutual": is_mutual,
|
||||||
|
"is_char_uncapped_override": character.is_uncapped_override,
|
||||||
|
"is_char_uncapped": character.is_uncapped,
|
||||||
|
"is_skill_sealed": you.is_skill_sealed,
|
||||||
|
"rating": rating,
|
||||||
|
"join_date": you.join_date,
|
||||||
|
"character": character.character_id,
|
||||||
|
"recent_score": you.recent_score_list,
|
||||||
|
"name": you.name,
|
||||||
|
"user_id": you.user_id
|
||||||
|
})
|
||||||
|
s.sort(key=lambda item: item["recent_score"][0]["time_played"] if len(
|
||||||
|
item["recent_score"]) > 0 else 0, reverse=True)
|
||||||
|
self.__friends = s
|
||||||
|
|
||||||
|
return self.__friends
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recent_score_list(self) -> list:
|
||||||
|
# 用户最近一次成绩,是列表
|
||||||
|
if self.name is None:
|
||||||
|
self.select_user()
|
||||||
|
|
||||||
|
if self.recent_score.song.song_id is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
self.c.execute('''select best_clear_type from best_score where user_id=:u and song_id=:s and difficulty=:d''', {
|
||||||
|
'u': self.user_id, 's': self.recent_score.song.song_id, 'd': self.recent_score.song.difficulty})
|
||||||
|
y = self.c.fetchone()
|
||||||
|
best_clear_type = y[0] if y is not None else self.recent_score.clear_type
|
||||||
|
|
||||||
|
r = self.recent_score.to_dict()
|
||||||
|
r["best_clear_type"] = best_clear_type
|
||||||
|
return [r]
|
||||||
|
|
||||||
|
def select_curr_available_maps(self) -> None:
|
||||||
|
self.curr_available_maps: list = []
|
||||||
|
for i in Config.AVAILABLE_MAP:
|
||||||
|
self.curr_available_maps.append(Map(i))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def curr_available_maps_list(self) -> list:
|
||||||
|
if self.curr_available_maps is None:
|
||||||
|
self.select_curr_available_maps()
|
||||||
|
return [x.to_dict() for x in self.curr_available_maps]
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
'''返回用户信息的字典,其实就是/user/me'''
|
||||||
|
if self.name is None:
|
||||||
|
self.select_user()
|
||||||
|
|
||||||
|
# 这是考虑有可能favourite_character设置了用户未拥有的角色,同时提前计算角色列表
|
||||||
|
character_list = self.characters_list
|
||||||
|
if self.favorite_character and self.favorite_character.character_id in character_list:
|
||||||
|
favorite_character_id = self.favorite_character.character_id
|
||||||
|
else:
|
||||||
|
favorite_character_id = -1
|
||||||
|
return {
|
||||||
|
"is_aprilfools": Config.IS_APRILFOOLS,
|
||||||
|
"curr_available_maps": self.curr_available_maps_list,
|
||||||
|
"character_stats": [x.to_dict() for x in self.characters.characters],
|
||||||
|
"friends": self.friends,
|
||||||
|
"settings": {
|
||||||
|
"favorite_character": favorite_character_id,
|
||||||
|
"is_hide_rating": self.is_hide_rating,
|
||||||
|
"max_stamina_notification_enabled": self.max_stamina_notification_enabled
|
||||||
|
},
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"name": self.name,
|
||||||
|
"user_code": self.user_code,
|
||||||
|
"display_name": self.name,
|
||||||
|
"ticket": self.ticket,
|
||||||
|
"character": self.character.character_id,
|
||||||
|
"is_locked_name_duplicate": False,
|
||||||
|
"is_skill_sealed": self.is_skill_sealed,
|
||||||
|
"current_map": self.current_map.map_id,
|
||||||
|
"prog_boost": self.prog_boost,
|
||||||
|
"next_fragstam_ts": self.next_fragstam_ts,
|
||||||
|
"max_stamina_ts": self.stamina.max_stamina_ts,
|
||||||
|
"stamina": self.stamina.stamina,
|
||||||
|
"world_unlocks": self.world_unlocks,
|
||||||
|
"world_songs": self.world_songs,
|
||||||
|
"singles": self.singles,
|
||||||
|
"packs": self.packs,
|
||||||
|
"characters": self.characters_list,
|
||||||
|
"cores": self.cores,
|
||||||
|
"recent_score": self.recent_score_list,
|
||||||
|
"max_friend": Constant.MAX_FRIEND_COUNT,
|
||||||
|
"rating": self.rating_ptt,
|
||||||
|
"join_date": self.join_date,
|
||||||
|
"global_rank": self.global_rank,
|
||||||
|
'country': '',
|
||||||
|
'course_banners': self.course_banners,
|
||||||
|
'world_mode_locked_end_ts': self.world_mode_locked_end_ts,
|
||||||
|
'locked_char_ids': [] # [1]
|
||||||
|
}
|
||||||
|
|
||||||
|
def from_list(self, x: list) -> 'UserInfo':
|
||||||
|
'''从数据库user表全部数据获取信息'''
|
||||||
|
if not x:
|
||||||
|
return None
|
||||||
|
if self.user_id is None:
|
||||||
|
self.user_id = x[0]
|
||||||
|
self.name = x[1]
|
||||||
|
self.join_date = int(x[3])
|
||||||
|
self.user_code = x[4]
|
||||||
|
self.rating_ptt = x[5]
|
||||||
|
self.character = UserCharacter(self.c, x[6])
|
||||||
|
self.is_skill_sealed = x[7] == 1
|
||||||
|
self.character.is_uncapped = x[8] == 1
|
||||||
|
self.character.is_uncapped_override = x[9] == 1
|
||||||
|
self.is_hide_rating = x[10] == 1
|
||||||
|
|
||||||
|
self.recent_score.song.song_id = x[11]
|
||||||
|
self.recent_score.song.difficulty = x[12]
|
||||||
|
self.recent_score.set_score(
|
||||||
|
x[13], x[14], x[15], x[16], x[17], x[18], x[19], x[20], x[21])
|
||||||
|
self.recent_score.rating = x[22]
|
||||||
|
|
||||||
|
self.favorite_character = None if x[23] == - \
|
||||||
|
1 else UserCharacter(self.c, x[23])
|
||||||
|
self.max_stamina_notification_enabled = x[24] == 1
|
||||||
|
self.current_map = Map(x[25]) if x[25] is not None else Map('')
|
||||||
|
self.ticket = x[26]
|
||||||
|
self.prog_boost = x[27] if x[27] is not None else 0
|
||||||
|
self.email = x[28] if x[28] is not None else ''
|
||||||
|
self.world_rank_score = x[29] if x[29] is not None else 0
|
||||||
|
self.ban_flag = x[30] if x[30] is not None else ''
|
||||||
|
|
||||||
|
self.next_fragstam_ts = x[31] if x[31] else 0
|
||||||
|
|
||||||
|
self.stamina = UserStamina(self.c, self)
|
||||||
|
self.stamina.set_value(x[32], x[33])
|
||||||
|
self.world_mode_locked_end_ts = x[34] if x[34] else -1
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_user(self) -> None:
|
||||||
|
# 查user表所有信息
|
||||||
|
self.c.execute(
|
||||||
|
'''select * from user where user_id = :x''', {'x': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('No user.', 108, -3)
|
||||||
|
|
||||||
|
self.from_list(x)
|
||||||
|
|
||||||
|
def select_user_about_current_map(self) -> None:
|
||||||
|
self.c.execute('''select current_map from user where user_id = :a''',
|
||||||
|
{'a': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.current_map = Map(x[0])
|
||||||
|
|
||||||
|
def select_user_about_stamina(self) -> None:
|
||||||
|
self.c.execute('''select max_stamina_ts, stamina from user where user_id = :a''',
|
||||||
|
{'a': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('No user.', 108, -3)
|
||||||
|
|
||||||
|
self.stamina = UserStamina(self.c, self)
|
||||||
|
self.stamina.set_value(x[0], x[1])
|
||||||
|
|
||||||
|
def select_user_about_character(self) -> None:
|
||||||
|
'''
|
||||||
|
查询user表有关角色的信息
|
||||||
|
'''
|
||||||
|
self.c.execute('''select name, character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, favorite_character from user where user_id = :a''', {
|
||||||
|
'a': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('No user.', 108, -3)
|
||||||
|
|
||||||
|
self.name = x[0]
|
||||||
|
self.character = UserCharacter(self.c, x[1], self)
|
||||||
|
self.is_skill_sealed = x[2] == 1
|
||||||
|
self.character.is_uncapped = x[3] == 1
|
||||||
|
self.character.is_uncapped_override = x[4] == 1
|
||||||
|
self.favorite_character = None if x[5] == - \
|
||||||
|
1 else UserCharacter(self.c, x[5], self)
|
||||||
|
|
||||||
|
def select_user_about_world_play(self) -> None:
|
||||||
|
'''
|
||||||
|
查询user表有关世界模式打歌的信息
|
||||||
|
'''
|
||||||
|
self.c.execute(
|
||||||
|
'''select character_id, max_stamina_ts, stamina, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, current_map, world_mode_locked_end_ts from user where user_id=?''', (self.user_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('No user.', 108, -3)
|
||||||
|
|
||||||
|
self.character = UserCharacter(self.c, x[0], self)
|
||||||
|
self.stamina = UserStamina(self.c, self)
|
||||||
|
self.stamina.set_value(x[1], x[2])
|
||||||
|
self.is_skill_sealed = x[3] == 1
|
||||||
|
self.character.is_uncapped = x[4] == 1
|
||||||
|
self.character.is_uncapped_override = x[5] == 1
|
||||||
|
self.current_map = UserMap(self.c, x[6], self)
|
||||||
|
self.world_mode_locked_end_ts = x[7] if x[7] else -1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def global_rank(self) -> int:
|
||||||
|
'''用户世界排名,如果超过设定最大值,返回0'''
|
||||||
|
if self.world_rank_score is None:
|
||||||
|
self.select_user_one_column('world_rank_score', 0)
|
||||||
|
if self.world_rank_score is None:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
self.c.execute(
|
||||||
|
'''select count(*) from user where world_rank_score > ?''', (self.world_rank_score,))
|
||||||
|
y = self.c.fetchone()
|
||||||
|
if y and y[0] + 1 <= Config.WORLD_RANK_MAX:
|
||||||
|
return y[0] + 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def update_global_rank(self) -> None:
|
||||||
|
'''用户世界排名计算,有新增成绩则要更新'''
|
||||||
|
with Connect() as c2:
|
||||||
|
c2.execute('''select song_id, rating_ftr, rating_byn from chart''')
|
||||||
|
x = c2.fetchall()
|
||||||
|
if x:
|
||||||
|
song_list_ftr = [self.user_id]
|
||||||
|
song_list_byn = [self.user_id]
|
||||||
|
for i in x:
|
||||||
|
if i[1] > 0:
|
||||||
|
song_list_ftr.append(i[0])
|
||||||
|
if i[2] > 0:
|
||||||
|
song_list_byn.append(i[0])
|
||||||
|
|
||||||
|
if len(song_list_ftr) >= 2:
|
||||||
|
self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=2 and song_id in ({0})'''.format(
|
||||||
|
','.join(['?']*(len(song_list_ftr)-1))), tuple(song_list_ftr))
|
||||||
|
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x[0] is not None:
|
||||||
|
score_sum = x[0]
|
||||||
|
else:
|
||||||
|
score_sum = 0
|
||||||
|
|
||||||
|
if len(song_list_byn) >= 2:
|
||||||
|
self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=3 and song_id in ({0})'''.format(
|
||||||
|
','.join(['?']*(len(song_list_byn)-1))), tuple(song_list_byn))
|
||||||
|
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x[0] is not None:
|
||||||
|
score_sum += x[0]
|
||||||
|
else:
|
||||||
|
score_sum += 0
|
||||||
|
|
||||||
|
self.c.execute('''update user set world_rank_score = :b where user_id = :a''', {
|
||||||
|
'a': self.user_id, 'b': score_sum})
|
||||||
|
|
||||||
|
self.world_rank_score = score_sum
|
||||||
|
|
||||||
|
def select_user_one_column(self, column_name: str, default_value=None) -> None:
|
||||||
|
'''
|
||||||
|
查询user表的某个属性\
|
||||||
|
请注意必须是一个普通属性,不能是一个类的实例
|
||||||
|
'''
|
||||||
|
if column_name not in self.__dict__:
|
||||||
|
raise InputError('No such column.')
|
||||||
|
self.c.execute('''select %s from user where user_id = :a''' %
|
||||||
|
column_name, {'a': self.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('No user.', 108, -3)
|
||||||
|
|
||||||
|
self.__dict__[column_name] = x[0] if x[0] else default_value
|
||||||
|
|
||||||
|
def update_user_one_column(self, column_name: str, value=None) -> None:
|
||||||
|
'''
|
||||||
|
更新user表的某个属性\
|
||||||
|
请注意必须是一个普通属性,不能是一个类的实例
|
||||||
|
'''
|
||||||
|
if column_name not in self.__dict__:
|
||||||
|
raise InputError('No such column.')
|
||||||
|
if value is not None:
|
||||||
|
self.__dict__[column_name] = value
|
||||||
|
self.c.execute('''update user set %s = :a where user_id = :b''' %
|
||||||
|
column_name, {'a': self.__dict__[column_name], 'b': self.user_id})
|
||||||
|
|
||||||
|
|
||||||
|
class UserOnline(UserInfo):
|
||||||
|
|
||||||
|
def __init__(self, c, user_id=None) -> None:
|
||||||
|
super().__init__(c, user_id)
|
||||||
|
|
||||||
|
def change_character(self, character_id: int, skill_sealed: bool = False):
|
||||||
|
'''用户角色改变,包括技能封印的改变'''
|
||||||
|
self.character = UserCharacter(self.c, character_id, self)
|
||||||
|
self.character.select_character_uncap_condition()
|
||||||
|
self.is_skill_sealed = skill_sealed
|
||||||
|
|
||||||
|
self.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': 1 if self.is_skill_sealed else 0, 'b': self.character.character_id, 'c': self.character.is_uncapped, 'd': self.character.is_uncapped_override, 'e': self.user_id})
|
||||||
|
|
||||||
|
def add_friend(self, friend_id: int):
|
||||||
|
'''加好友'''
|
||||||
|
if self.user_id == friend_id:
|
||||||
|
raise FriendError('Add yourself as a friend.', 604)
|
||||||
|
|
||||||
|
self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
||||||
|
{'x': self.user_id, 'y': friend_id})
|
||||||
|
if self.c.fetchone() == (0,):
|
||||||
|
self.c.execute('''insert into friend values(:a, :b)''', {
|
||||||
|
'a': self.user_id, 'b': friend_id})
|
||||||
|
else:
|
||||||
|
raise FriendError('The user has been your friend.', 602)
|
||||||
|
|
||||||
|
def delete_friend(self, friend_id: int):
|
||||||
|
'''删好友'''
|
||||||
|
self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
||||||
|
{'x': self.user_id, 'y': friend_id})
|
||||||
|
if self.c.fetchone() == (1,):
|
||||||
|
self.c.execute('''delete from friend where user_id_me = :x and user_id_other = :y''',
|
||||||
|
{'x': self.user_id, 'y': friend_id})
|
||||||
|
else:
|
||||||
|
raise FriendError('No user or the user is not your friend.', 401)
|
||||||
|
|
||||||
|
def change_favorite_character(self, character_id: int) -> None:
|
||||||
|
'''更改用户的favorite_character'''
|
||||||
|
self.favorite_character = UserCharacter(self.c, character_id, self)
|
||||||
|
self.c.execute('''update user set favorite_character = :a where user_id = :b''',
|
||||||
|
{'a': self.favorite_character.character_id, 'b': self.user_id})
|
||||||
27
latest version/core/util.py
Normal file
27
latest version/core/util.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def md5(code):
|
||||||
|
# md5加密算法
|
||||||
|
code = code.encode()
|
||||||
|
md5s = hashlib.md5()
|
||||||
|
md5s.update(code)
|
||||||
|
codes = md5s.hexdigest()
|
||||||
|
|
||||||
|
return codes
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
668
latest version/core/world.py
Normal file
668
latest version/core/world.py
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from functools import lru_cache
|
||||||
|
from random import random
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from .character import Character
|
||||||
|
from .constant import Constant
|
||||||
|
from .error import InputError, MapLocked, NoData
|
||||||
|
from .item import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=128)
|
||||||
|
def get_world_name(file_dir: str = Constant.WORLD_MAP_FOLDER_PATH) -> list:
|
||||||
|
'''获取所有地图名称,返回列表'''
|
||||||
|
file_list = []
|
||||||
|
for root, dirs, files in os.walk(file_dir):
|
||||||
|
for file in files:
|
||||||
|
if os.path.splitext(file)[1] == '.json':
|
||||||
|
file_list.append(os.path.splitext(file)[0])
|
||||||
|
return file_list
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=128)
|
||||||
|
def get_world_info(map_id: str) -> dict:
|
||||||
|
'''读取json文件内容,返回字典'''
|
||||||
|
world_info = {}
|
||||||
|
with open(os.path.join(Constant.WORLD_MAP_FOLDER_PATH, map_id+'.json'), 'r') as f:
|
||||||
|
world_info = json.load(f)
|
||||||
|
|
||||||
|
return world_info
|
||||||
|
|
||||||
|
|
||||||
|
def get_world_all(c, user) -> list:
|
||||||
|
'''
|
||||||
|
读取所有地图信息,返回列表\
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
worlds = get_world_name()
|
||||||
|
return [UserMap(c, map_id, user) for map_id in worlds]
|
||||||
|
|
||||||
|
|
||||||
|
class Step:
|
||||||
|
'''台阶类'''
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.postion: int = None
|
||||||
|
self.capture: int = None
|
||||||
|
self.items: list = []
|
||||||
|
self.restrict_id: str = None
|
||||||
|
self.restrict_ids: list = []
|
||||||
|
self.restrict_type: str = None
|
||||||
|
self.step_type: list = None
|
||||||
|
self.speed_limit_value: int = None
|
||||||
|
self.plus_stamina_value: int = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
r = {
|
||||||
|
'position': self.position,
|
||||||
|
'capture': self.capture,
|
||||||
|
}
|
||||||
|
if self.items:
|
||||||
|
r['items'] = [i.to_dict() for i in self.items]
|
||||||
|
if self.restrict_id:
|
||||||
|
r['restrict_id'] = self.restrict_id
|
||||||
|
if self.restrict_ids:
|
||||||
|
r['restrict_ids'] = self.restrict_ids
|
||||||
|
if self.restrict_type:
|
||||||
|
r['restrict_type'] = self.restrict_type
|
||||||
|
if self.step_type:
|
||||||
|
r['step_type'] = self.step_type
|
||||||
|
if self.speed_limit_value:
|
||||||
|
r['speed_limit_value'] = self.speed_limit_value
|
||||||
|
if self.plus_stamina_value:
|
||||||
|
r['plus_stamina_value'] = self.plus_stamina_value
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def from_dict(self, d: dict) -> 'Step':
|
||||||
|
self.position = d['position']
|
||||||
|
self.capture = d['capture']
|
||||||
|
self.restrict_id = d.get('restrict_id')
|
||||||
|
self.restrict_ids = d.get('restrict_ids')
|
||||||
|
self.restrict_type = d.get('restrict_type')
|
||||||
|
self.step_type = d.get('step_type')
|
||||||
|
self.speed_limit_value = d.get('speed_limit_value')
|
||||||
|
self.plus_stamina_value = d.get('plus_stamina_value')
|
||||||
|
if 'items' in d:
|
||||||
|
self.items = [ItemFactory.from_dict(i) for i in d['items']]
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class Map:
|
||||||
|
def __init__(self, map_id: str = None) -> None:
|
||||||
|
self.map_id: str = map_id
|
||||||
|
self.is_legacy: bool = None
|
||||||
|
self.is_beyond: bool = None
|
||||||
|
self.beyond_health: int = None
|
||||||
|
self.character_affinity: list = []
|
||||||
|
self.affinity_multiplier: list = []
|
||||||
|
self.chapter: int = None
|
||||||
|
self.available_from: int = None
|
||||||
|
self.available_to: int = None
|
||||||
|
self.is_repeatable: bool = None
|
||||||
|
self.require_id: str = None
|
||||||
|
self.require_type: str = None
|
||||||
|
self.require_value: int = None
|
||||||
|
self.coordinate: str = None
|
||||||
|
self.custom_bg: str = None
|
||||||
|
self.stamina_cost: int = None
|
||||||
|
self.steps: list = []
|
||||||
|
self.__rewards: list = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rewards(self) -> list:
|
||||||
|
if self.__rewards is None:
|
||||||
|
self.get_rewards()
|
||||||
|
return self.__rewards
|
||||||
|
|
||||||
|
def get_rewards(self) -> list:
|
||||||
|
if self.steps:
|
||||||
|
self.__rewards = []
|
||||||
|
for step in self.steps:
|
||||||
|
if step.items:
|
||||||
|
self.__rewards.append(
|
||||||
|
{'items': [i.to_dict() for i in step.items], 'position': step.position})
|
||||||
|
return self.__rewards
|
||||||
|
|
||||||
|
@property
|
||||||
|
def step_count(self):
|
||||||
|
return len(self.steps)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
if self.chapter is None:
|
||||||
|
self.select_map_info()
|
||||||
|
return {
|
||||||
|
'map_id': self.map_id,
|
||||||
|
'is_legacy': self.is_legacy,
|
||||||
|
'is_beyond': self.is_beyond,
|
||||||
|
'beyond_health': self.beyond_health,
|
||||||
|
'character_affinity': self.character_affinity,
|
||||||
|
'affinity_multiplier': self.affinity_multiplier,
|
||||||
|
'chapter': self.chapter,
|
||||||
|
'available_from': self.available_from,
|
||||||
|
'available_to': self.available_to,
|
||||||
|
'is_repeatable': self.is_repeatable,
|
||||||
|
'require_id': self.require_id,
|
||||||
|
'require_type': self.require_type,
|
||||||
|
'require_value': self.require_value,
|
||||||
|
'coordinate': self.coordinate,
|
||||||
|
'custom_bg': self.custom_bg,
|
||||||
|
'stamina_cost': self.stamina_cost,
|
||||||
|
'step_count': self.step_count,
|
||||||
|
'steps': [s.to_dict() for s in self.steps],
|
||||||
|
}
|
||||||
|
|
||||||
|
def from_dict(self, raw_dict: dict) -> 'Map':
|
||||||
|
self.is_legacy = raw_dict.get('is_legacy')
|
||||||
|
self.is_beyond = raw_dict.get('is_beyond')
|
||||||
|
self.beyond_health = raw_dict.get('beyond_health')
|
||||||
|
self.character_affinity = raw_dict.get('character_affinity')
|
||||||
|
self.affinity_multiplier = raw_dict.get('affinity_multiplier')
|
||||||
|
self.chapter = raw_dict.get('chapter')
|
||||||
|
self.available_from = raw_dict.get('available_from')
|
||||||
|
self.available_to = raw_dict.get('available_to')
|
||||||
|
self.is_repeatable = raw_dict.get('is_repeatable')
|
||||||
|
self.require_id = raw_dict.get('require_id')
|
||||||
|
self.require_type = raw_dict.get('require_type')
|
||||||
|
self.require_value = raw_dict.get('require_value')
|
||||||
|
self.coordinate = raw_dict.get('coordinate')
|
||||||
|
self.custom_bg = raw_dict.get('custom_bg')
|
||||||
|
self.stamina_cost = raw_dict.get('stamina_cost')
|
||||||
|
self.steps = [Step().from_dict(s) for s in raw_dict.get('steps')]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_map_info(self):
|
||||||
|
'''获取地图信息'''
|
||||||
|
self.from_dict(get_world_info(self.map_id))
|
||||||
|
|
||||||
|
|
||||||
|
class UserMap(Map):
|
||||||
|
'''
|
||||||
|
用户地图类\
|
||||||
|
parameters: `user` - `User`类或者子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, map_id: str = None, user=None) -> None:
|
||||||
|
super().__init__(map_id)
|
||||||
|
self.c = c
|
||||||
|
self.curr_position: int = None
|
||||||
|
self.curr_capture: int = None
|
||||||
|
self.is_locked: bool = None
|
||||||
|
|
||||||
|
self.prev_position: int = None
|
||||||
|
self.prev_capture: int = None
|
||||||
|
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rewards_for_climbing(self) -> list:
|
||||||
|
rewards = []
|
||||||
|
for i in range(self.prev_position, self.curr_position+1):
|
||||||
|
step = self.steps[i]
|
||||||
|
if step.items:
|
||||||
|
rewards.append(
|
||||||
|
{'items': step.items, 'position': step.position})
|
||||||
|
|
||||||
|
return rewards
|
||||||
|
|
||||||
|
def rewards_for_climbing_to_dict(self) -> list:
|
||||||
|
rewards = []
|
||||||
|
for i in range(self.prev_position, self.curr_position+1):
|
||||||
|
step = self.steps[i]
|
||||||
|
if step.items:
|
||||||
|
rewards.append(
|
||||||
|
{'items': [i.to_dict() for i in step.items], 'position': step.position})
|
||||||
|
|
||||||
|
return rewards
|
||||||
|
|
||||||
|
@property
|
||||||
|
def steps_for_climbing(self) -> list:
|
||||||
|
return self.steps[self.prev_position:self.curr_position+1]
|
||||||
|
|
||||||
|
def to_dict(self, has_map_info: bool = False, has_steps: bool = False, has_rewards: bool = False) -> dict:
|
||||||
|
if self.is_locked is None:
|
||||||
|
self.select()
|
||||||
|
if has_map_info:
|
||||||
|
if self.chapter is None:
|
||||||
|
self.select_map_info()
|
||||||
|
r = super().to_dict()
|
||||||
|
r['curr_position'] = self.curr_position
|
||||||
|
r['curr_capture'] = self.curr_capture
|
||||||
|
r['is_locked'] = self.is_locked
|
||||||
|
r['user_id'] = self.user.user_id
|
||||||
|
if not has_steps:
|
||||||
|
del r['steps']
|
||||||
|
if has_rewards:
|
||||||
|
r['rewards'] = self.rewards
|
||||||
|
else:
|
||||||
|
r = {
|
||||||
|
'map_id': self.map_id,
|
||||||
|
'curr_position': self.curr_position,
|
||||||
|
'curr_capture': self.curr_capture,
|
||||||
|
'is_locked': self.is_locked,
|
||||||
|
'user_id': self.user.user_id,
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
'''初始化数据库信息'''
|
||||||
|
self.c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
|
||||||
|
'a': self.user.user_id, 'b': self.map_id})
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
'''向数据库更新信息'''
|
||||||
|
self.c.execute('''update user_world set curr_position=:a,curr_capture=:b,is_locked=:c where user_id=:d and map_id=:e''', {
|
||||||
|
'a': self.curr_position, 'b': self.curr_capture, 'c': 1 if self.is_locked else 0, 'd': self.user.user_id, 'e': self.map_id})
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
'''获取用户在此地图的信息'''
|
||||||
|
self.c.execute('''select curr_position, curr_capture, is_locked from user_world where map_id = :a and user_id = :b''',
|
||||||
|
{'a': self.map_id, 'b': self.user.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if x:
|
||||||
|
self.curr_position = x[0]
|
||||||
|
self.curr_capture = x[1]
|
||||||
|
self.is_locked = x[2] == 1
|
||||||
|
else:
|
||||||
|
self.curr_position = 0
|
||||||
|
self.curr_capture = 0
|
||||||
|
self.is_locked = True
|
||||||
|
self.initialize()
|
||||||
|
|
||||||
|
def change_user_current_map(self):
|
||||||
|
'''改变用户当前地图为此地图'''
|
||||||
|
self.user.current_map = self
|
||||||
|
self.c.execute('''update user set current_map = :a where user_id=:b''', {
|
||||||
|
'a': self.map_id, 'b': self.user.user_id})
|
||||||
|
|
||||||
|
def unlock(self) -> bool:
|
||||||
|
'''解锁用户此地图,返回成功与否bool值'''
|
||||||
|
self.select()
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
self.is_locked = False
|
||||||
|
self.curr_position = 0
|
||||||
|
self.curr_capture = 0
|
||||||
|
self.select_map_info()
|
||||||
|
if self.require_type is not None and self.require_type != '':
|
||||||
|
if self.require_type in ['pack', 'single']:
|
||||||
|
item = ItemFactory(self.c).get_item(self.require_type)
|
||||||
|
item.item_id = self.require_id
|
||||||
|
item.select(self.user)
|
||||||
|
if not item.amount:
|
||||||
|
self.is_locked = True
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
return not self.is_locked
|
||||||
|
|
||||||
|
def climb(self, step_value: float) -> None:
|
||||||
|
'''爬梯子,数值非负'''
|
||||||
|
if step_value < 0:
|
||||||
|
raise InputError('`Step_value` must be non-negative.')
|
||||||
|
if self.curr_position is None:
|
||||||
|
self.select()
|
||||||
|
if self.is_beyond is None:
|
||||||
|
self.select_map_info()
|
||||||
|
if self.is_locked:
|
||||||
|
raise MapLocked('The map is locked.')
|
||||||
|
|
||||||
|
self.prev_capture = self.curr_capture
|
||||||
|
self.prev_position = self.curr_position
|
||||||
|
|
||||||
|
if self.is_beyond: # beyond判断
|
||||||
|
dt = self.beyond_health - self.prev_capture
|
||||||
|
self.curr_capture = self.prev_capture + \
|
||||||
|
step_value if dt >= step_value else self.beyond_health
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
t = self.prev_capture + step_value
|
||||||
|
while i < self.step_count and t > 0:
|
||||||
|
dt = self.steps[i].capture
|
||||||
|
if dt > t:
|
||||||
|
t = 0
|
||||||
|
else:
|
||||||
|
t -= dt
|
||||||
|
i += 1
|
||||||
|
if i >= self.step_count:
|
||||||
|
self.curr_position = self.step_count - 1
|
||||||
|
else:
|
||||||
|
self.curr_position = i
|
||||||
|
|
||||||
|
else:
|
||||||
|
i = self.prev_position
|
||||||
|
j = self.prev_capture
|
||||||
|
t = step_value
|
||||||
|
while t > 0 and i < self.step_count:
|
||||||
|
dt = self.steps[i].capture - j
|
||||||
|
if dt > t:
|
||||||
|
j += t
|
||||||
|
t = 0
|
||||||
|
else:
|
||||||
|
t -= dt
|
||||||
|
j = 0
|
||||||
|
i += 1
|
||||||
|
if i >= self.step_count:
|
||||||
|
self.curr_position = self.step_count - 1
|
||||||
|
self.curr_capture = 0
|
||||||
|
else:
|
||||||
|
self.curr_position = i
|
||||||
|
self.curr_capture = j
|
||||||
|
|
||||||
|
def reclimb(self, step_value: float) -> None:
|
||||||
|
'''重新爬梯子计算'''
|
||||||
|
self.curr_position = self.prev_position
|
||||||
|
self.curr_capture = self.prev_capture
|
||||||
|
self.climb(step_value)
|
||||||
|
|
||||||
|
|
||||||
|
class Stamina:
|
||||||
|
'''
|
||||||
|
体力类
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__stamina: int = None
|
||||||
|
self.max_stamina_ts: int = None
|
||||||
|
|
||||||
|
def set_value(self, max_stamina_ts: int, stamina: int):
|
||||||
|
self.max_stamina_ts = int(max_stamina_ts) if max_stamina_ts else 0
|
||||||
|
self.__stamina = int(stamina) if stamina else Constant.MAX_STAMINA
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stamina(self) -> int:
|
||||||
|
'''通过计算得到当前的正确体力值'''
|
||||||
|
stamina = round(Constant.MAX_STAMINA - (self.max_stamina_ts -
|
||||||
|
int(time()*1000)) / Constant.STAMINA_RECOVER_TICK)
|
||||||
|
|
||||||
|
if stamina >= Constant.MAX_STAMINA:
|
||||||
|
if self.__stamina >= Constant.MAX_STAMINA:
|
||||||
|
stamina = self.__stamina
|
||||||
|
else:
|
||||||
|
stamina = Constant.MAX_STAMINA
|
||||||
|
|
||||||
|
return stamina
|
||||||
|
|
||||||
|
@stamina.setter
|
||||||
|
def stamina(self, value: int) -> None:
|
||||||
|
'''设置体力值,此处会导致max_stamina_ts变化'''
|
||||||
|
self.__stamina = round(value)
|
||||||
|
self.max_stamina_ts = int(
|
||||||
|
time()*1000) - (self.__stamina-Constant.MAX_STAMINA) * Constant.STAMINA_RECOVER_TICK
|
||||||
|
|
||||||
|
|
||||||
|
class UserStamina(Stamina):
|
||||||
|
'''
|
||||||
|
用户体力类\
|
||||||
|
parameter: `user` - `User`类或子类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
'''获取用户体力信息'''
|
||||||
|
self.c.execute('''select max_stamina_ts, staminafrom user where user_id = :a''',
|
||||||
|
{'a': self.user.user_id})
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData('The user does not exist.')
|
||||||
|
self.set_value(x[0], x[1])
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
'''向数据库更新信息'''
|
||||||
|
self.c.execute('''update user set max_stamina_ts=:b, stamina=:a where user_id=:c''', {
|
||||||
|
'a': self.stamina, 'b': self.max_stamina_ts, 'c': self.user.user_id})
|
||||||
|
|
||||||
|
|
||||||
|
class WorldPlay:
|
||||||
|
'''
|
||||||
|
世界模式打歌类,处理特殊角色技能,联动UserMap和UserPlay\
|
||||||
|
parameter: `user` - `UserOnline`类或子类的实例\
|
||||||
|
'user_play` - `UserPlay`类的实例
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, c=None, user=None, user_play=None) -> None:
|
||||||
|
self.c = c
|
||||||
|
self.user = user
|
||||||
|
self.user_play = user_play
|
||||||
|
self.character_used = None
|
||||||
|
|
||||||
|
self.base_step_value: float = None
|
||||||
|
self.step_value: float = None
|
||||||
|
|
||||||
|
self.prog_tempest: float = None
|
||||||
|
self.overdrive_extra: float = None
|
||||||
|
self.character_bonus_progress: float = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
arcmap: 'UserMap' = self.user.current_map
|
||||||
|
r = {
|
||||||
|
"rewards": arcmap.rewards_for_climbing_to_dict(),
|
||||||
|
"exp": self.character_used.level.exp,
|
||||||
|
"level": self.character_used.level.level,
|
||||||
|
"base_progress": self.base_step_value,
|
||||||
|
"progress": self.step_value,
|
||||||
|
"user_map": {
|
||||||
|
"user_id": self.user.user_id,
|
||||||
|
"curr_position": arcmap.curr_position,
|
||||||
|
"curr_capture": arcmap.curr_capture,
|
||||||
|
"is_locked": arcmap.is_locked,
|
||||||
|
"map_id": arcmap.map_id,
|
||||||
|
"prev_capture": arcmap.prev_capture,
|
||||||
|
"prev_position": arcmap.prev_position,
|
||||||
|
"beyond_health": arcmap.beyond_health
|
||||||
|
},
|
||||||
|
"char_stats": {
|
||||||
|
"character_id": self.character_used.character_id,
|
||||||
|
"frag": self.character_used.frag.get_value(self.character_used.level),
|
||||||
|
"prog": self.character_used.prog.get_value(self.character_used.level),
|
||||||
|
"overdrive": self.character_used.overdrive.get_value(self.character_used.level)
|
||||||
|
},
|
||||||
|
"current_stamina": self.user.stamina.stamina,
|
||||||
|
"max_stamina_ts": self.user.stamina.max_stamina_ts,
|
||||||
|
'world_mode_locked_end_ts': self.user.world_mode_locked_end_ts
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.overdrive_extra is not None:
|
||||||
|
r['char_stats']['overdrive'] += self.overdrive_extra
|
||||||
|
|
||||||
|
if self.prog_tempest is not None:
|
||||||
|
r['char_stats']['prog'] += self.prog_tempest
|
||||||
|
r['char_stats']['prog_tempest'] = self.prog_tempest
|
||||||
|
|
||||||
|
if self.character_bonus_progress is not None:
|
||||||
|
# 猜的,为了让客户端正确显示,当然结果是没问题的
|
||||||
|
r['base_progress'] += self.character_bonus_progress
|
||||||
|
r['character_bonus_progress'] = self.character_bonus_progress
|
||||||
|
|
||||||
|
if self.user_play.beyond_gauge == 0:
|
||||||
|
r["user_map"]["steps"] = [
|
||||||
|
x.to_dict() for x in arcmap.steps_for_climbing]
|
||||||
|
else:
|
||||||
|
r["user_map"]["steps"] = len(arcmap.steps_for_climbing)
|
||||||
|
|
||||||
|
if self.user_play.stamina_multiply != 1:
|
||||||
|
r['stamina_multiply'] = self.user_play.stamina_multiply
|
||||||
|
if self.user_play.fragment_multiply != 100:
|
||||||
|
r['fragment_multiply'] = self.user_play.fragment_multiply
|
||||||
|
if self.user_play.prog_boost_multiply != 0:
|
||||||
|
r['prog_boost_multiply'] = self.user_play.prog_boost_multiply
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def step_times(self) -> float:
|
||||||
|
return self.user_play.stamina_multiply * self.user_play.fragment_multiply / 100 * (self.user_play.prog_boost_multiply+100) / 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exp_times(self) -> float:
|
||||||
|
return self.user_play.stamina_multiply * (self.user_play.prog_boost_multiply+100) / 100
|
||||||
|
|
||||||
|
def get_step(self) -> None:
|
||||||
|
if self.user_play.beyond_gauge == 0:
|
||||||
|
self.base_step_value = 2.5 + 2.45 * self.user_play.rating**0.5
|
||||||
|
prog = self.character_used.prog.get_value(
|
||||||
|
self.character_used.level)
|
||||||
|
if self.prog_tempest:
|
||||||
|
prog += self.prog_tempest
|
||||||
|
|
||||||
|
self.step_value = self.base_step_value * prog / 50 * self.step_times
|
||||||
|
else:
|
||||||
|
if self.user_play.clear_type == 0:
|
||||||
|
self.base_step_value = 25/28 + \
|
||||||
|
(self.user_play.rating)**0.5 * 0.43
|
||||||
|
else:
|
||||||
|
self.base_step_value = 75/28 + \
|
||||||
|
(self.user_play.rating)**0.5 * 0.43
|
||||||
|
|
||||||
|
if self.character_used.character_id in self.user.current_map.character_affinity:
|
||||||
|
affinity_multiplier = self.user.current_map.affinity_multiplier[self.user.current_map.character_affinity.index(
|
||||||
|
self.character_used.character_id)]
|
||||||
|
else:
|
||||||
|
affinity_multiplier = 1
|
||||||
|
|
||||||
|
overdrive = self.character_used.overdrive.get_value(
|
||||||
|
self.character_used.level)
|
||||||
|
if self.overdrive_extra:
|
||||||
|
overdrive += self.overdrive_extra
|
||||||
|
|
||||||
|
self.step_value = self.base_step_value * overdrive / \
|
||||||
|
50 * self.step_times * affinity_multiplier
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
'''世界模式更新'''
|
||||||
|
if self.user_play.prog_boost_multiply != 0:
|
||||||
|
self.user.update_user_one_column('prog_boost', 0)
|
||||||
|
|
||||||
|
self.user_play.clear_play_state()
|
||||||
|
self.user.select_user_about_world_play()
|
||||||
|
|
||||||
|
self.character_used = Character()
|
||||||
|
|
||||||
|
self.user.character.select_character_info()
|
||||||
|
if not self.user.is_skill_sealed:
|
||||||
|
self.character_used = self.user.character
|
||||||
|
else:
|
||||||
|
self.character_used.character_id = self.user.character.character_id
|
||||||
|
self.character_used.level.level = self.user.character.level.level
|
||||||
|
self.character_used.level.exp = self.user.character.level.exp
|
||||||
|
self.character_used.frag.set_parameter(50, 50, 50)
|
||||||
|
self.character_used.prog.set_parameter(50, 50, 50)
|
||||||
|
self.character_used.overdrive.set_parameter(50, 50, 50)
|
||||||
|
|
||||||
|
self.user.current_map.select_map_info()
|
||||||
|
self.before_calculate()
|
||||||
|
self.get_step()
|
||||||
|
self.user.current_map.climb(self.step_value)
|
||||||
|
self.after_climb()
|
||||||
|
|
||||||
|
for i in self.user.current_map.rewards_for_climbing: # 物品分发
|
||||||
|
for j in i['items']:
|
||||||
|
j.c = self.c
|
||||||
|
j.user_claim_item(self.user)
|
||||||
|
|
||||||
|
x: 'Step' = self.user.current_map.steps_for_climbing[-1]
|
||||||
|
if x.step_type:
|
||||||
|
if 'plusstamina' in x.step_type and x.plus_stamina_value:
|
||||||
|
# 体力格子
|
||||||
|
self.user.stamina.stamina += x.plus_stamina_value
|
||||||
|
self.user.stamina.update()
|
||||||
|
|
||||||
|
# 角色升级
|
||||||
|
if self.character_used.database_table_name == 'user_char':
|
||||||
|
self.character_used.upgrade(
|
||||||
|
self.user, self.exp_times*self.user_play.rating*6)
|
||||||
|
|
||||||
|
if self.user.current_map.curr_position == self.user.current_map.step_count-1 and self.user.current_map.is_repeatable: # 循环图判断
|
||||||
|
self.user.current_map.curr_position = 0
|
||||||
|
|
||||||
|
self.user.current_map.update()
|
||||||
|
|
||||||
|
def before_calculate(self) -> None:
|
||||||
|
if self.user_play.beyond_gauge == 0:
|
||||||
|
if self.character_used.character_id == 35 and self.character_used.skill_id_displayed:
|
||||||
|
self._special_tempest()
|
||||||
|
else:
|
||||||
|
if self.character_used.skill_id_displayed == 'skill_vita':
|
||||||
|
self._skill_vita()
|
||||||
|
|
||||||
|
def after_climb(self) -> None:
|
||||||
|
factory_dict = {'eto_uncap': self._eto_uncap, 'ayu_uncap': self._ayu_uncap,
|
||||||
|
'luna_uncap': self._luna_uncap, 'skill_fatalis': self._skill_fatalis}
|
||||||
|
if self.character_used.skill_id_displayed in factory_dict:
|
||||||
|
factory_dict[self.character_used.skill_id_displayed]()
|
||||||
|
|
||||||
|
def _special_tempest(self) -> None:
|
||||||
|
'''风暴对立技能,prog随全角色等级提升'''
|
||||||
|
if self.character_used.database_table_name == 'user_char_full':
|
||||||
|
self.prog_tempest = 60
|
||||||
|
else:
|
||||||
|
self.c.execute(
|
||||||
|
'''select sum(level) from user_char where user_id=?''', (self.user.user_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
self.prog_tempest = int(x[0]) / 10 if x else 0
|
||||||
|
if self.prog_tempest > 60:
|
||||||
|
self.prog_tempest = 60
|
||||||
|
elif self.prog_tempest < 0:
|
||||||
|
self.prog_tempest = 0
|
||||||
|
|
||||||
|
def _skill_vita(self) -> None:
|
||||||
|
'''
|
||||||
|
vita技能,overdrive随回忆率提升,提升量最多为10\
|
||||||
|
此处采用线性函数
|
||||||
|
'''
|
||||||
|
self.overdrive_extra = 0
|
||||||
|
if 0 < self.user_play.health <= 100:
|
||||||
|
self.overdrive_extra = self.user_play.health / 10
|
||||||
|
|
||||||
|
def _eto_uncap(self) -> None:
|
||||||
|
'''eto觉醒技能,获得残片奖励时世界模式进度加7'''
|
||||||
|
fragment_flag = False
|
||||||
|
|
||||||
|
for i in self.user.current_map.rewards_for_climbing:
|
||||||
|
for j in i['items']:
|
||||||
|
if j.item_type == 'fragment':
|
||||||
|
fragment_flag = True
|
||||||
|
break
|
||||||
|
if fragment_flag:
|
||||||
|
break
|
||||||
|
|
||||||
|
if fragment_flag:
|
||||||
|
self.character_bonus_progress = Constant.ETO_UNCAP_BONUS_PROGRESS
|
||||||
|
self.step_value += self.character_bonus_progress * self.step_times
|
||||||
|
|
||||||
|
self.user.current_map.reclimb(self.step_value)
|
||||||
|
|
||||||
|
def _luna_uncap(self) -> None:
|
||||||
|
'''luna觉醒技能,限制格开始时世界模式进度加7'''
|
||||||
|
x: 'Step' = self.user.current_map.steps_for_climbing[0]
|
||||||
|
if x.restrict_id and x.restrict_type:
|
||||||
|
self.character_bonus_progress = Constant.LUNA_UNCAP_BONUS_PROGRESS
|
||||||
|
self.step_value += self.character_bonus_progress * self.step_times
|
||||||
|
|
||||||
|
self.user.current_map.reclimb(self.step_value)
|
||||||
|
|
||||||
|
def _ayu_uncap(self) -> None:
|
||||||
|
'''ayu觉醒技能,世界模式进度+5或-5,但不会小于0'''
|
||||||
|
|
||||||
|
self.character_bonus_progress = Constant.AYU_UNCAP_BONUS_PROGRESS if random(
|
||||||
|
) >= 0.5 else -Constant.AYU_UNCAP_BONUS_PROGRESS
|
||||||
|
|
||||||
|
self.step_value += self.character_bonus_progress * self.step_times
|
||||||
|
if self.step_value < 0:
|
||||||
|
self.character_bonus_progress += self.step_value / self.step_times
|
||||||
|
self.step_value = 0
|
||||||
|
|
||||||
|
self.user.current_map.reclimb(self.step_value)
|
||||||
|
|
||||||
|
def _skill_fatalis(self) -> None:
|
||||||
|
'''hikari fatalis技能,世界模式超载,打完休息60分钟'''
|
||||||
|
|
||||||
|
self.user.world_mode_locked_end_ts = int(
|
||||||
|
time()*1000) + Constant.SKILL_FATALIS_WORLD_LOCKED_TIME
|
||||||
|
self.user.update_user_one_column('world_mode_locked_end_ts')
|
||||||
0
latest version/database/__init__.py
Normal file
0
latest version/database/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
449
latest version/database/courses.json
Normal file
449
latest version/database/courses.json
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-1",
|
||||||
|
"style": 1,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 1,
|
||||||
|
"id": "inkarusi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 1,
|
||||||
|
"id": "snowwhite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 1,
|
||||||
|
"id": "sakurafubuki"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 1,
|
||||||
|
"id": "purgatorium"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 1",
|
||||||
|
"course_name": "新世界的第一步",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment300",
|
||||||
|
"course_banner_1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-2",
|
||||||
|
"style": 2,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "vexaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "clotho"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "brandnewworld"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "oblivia"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 2",
|
||||||
|
"course_name": "一次心跳的加速",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment400",
|
||||||
|
"course_banner_2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-3",
|
||||||
|
"style": 3,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "suomi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "grimheart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "bookmaker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "avril"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 3",
|
||||||
|
"course_name": "永不屈服的精神",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment500",
|
||||||
|
"course_banner_3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-4",
|
||||||
|
"style": 4,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "gekka"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "solitarydream"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "kanagawa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "iconoclast"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 4",
|
||||||
|
"course_name": "光芒的永恒之地",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment600",
|
||||||
|
"course_banner_4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-5",
|
||||||
|
"style": 5,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "faintlight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "eveninginscarlet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "kyogenkigo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "blrink"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 5",
|
||||||
|
"course_name": "玻璃之中的残酷",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment700",
|
||||||
|
"course_banner_5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-6",
|
||||||
|
"style": 6,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "vindication"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "heartjackin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "ascent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "macromod"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 6",
|
||||||
|
"course_name": "沉浸于悲喜交加",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment800",
|
||||||
|
"course_banner_6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-7",
|
||||||
|
"style": 7,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "galaxyfriends"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "neokosmo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "conflict"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "redolentshape"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 7",
|
||||||
|
"course_name": "在命运到来之际",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment900",
|
||||||
|
"course_banner_7"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-8",
|
||||||
|
"style": 8,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "corruption"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "neowings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "valhallazero"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "izana"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": false,
|
||||||
|
"dan_name": "Phase 8",
|
||||||
|
"course_name": "趋于混沌的时空",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment1000",
|
||||||
|
"course_banner_8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-9",
|
||||||
|
"style": 9,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "felis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "lightningscrew"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "seclusion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": false,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "livefastdieyoung"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"value": "4.0-dan-8",
|
||||||
|
"type": "course"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": true,
|
||||||
|
"dan_name": "Phase 9",
|
||||||
|
"course_name": "分崩离析的自我",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment1000",
|
||||||
|
"core_generic_10",
|
||||||
|
"course_banner_9"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-10",
|
||||||
|
"style": 10,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "cyaegha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "divinelight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "dantalion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "aegleseeker"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"value": "4.0-dan-9",
|
||||||
|
"type": "course"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": true,
|
||||||
|
"dan_name": "Phase 10",
|
||||||
|
"course_name": "光与对立的洪流",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment1000",
|
||||||
|
"core_generic_10",
|
||||||
|
"course_banner_10"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"course_id": "4.0-dan-11",
|
||||||
|
"style": 11,
|
||||||
|
"songs": [
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 3,
|
||||||
|
"id": "infinitestrife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "grievouslady"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 2,
|
||||||
|
"id": "fractureray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag_as_hidden": true,
|
||||||
|
"difficulty": 3,
|
||||||
|
"id": "tempestissimo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_requirement": "default",
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"value": "4.0-dan-10",
|
||||||
|
"type": "course"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flag_as_hidden_when_requirements_not_met": true,
|
||||||
|
"dan_name": "Phase 11",
|
||||||
|
"course_name": "创世之曙光",
|
||||||
|
"can_start": true,
|
||||||
|
"is_completed": false,
|
||||||
|
"high_score": 0,
|
||||||
|
"best_clear_type": -1,
|
||||||
|
"rewards": [
|
||||||
|
"fragment2000",
|
||||||
|
"core_generic_20",
|
||||||
|
"course_banner_11"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,254 +1,453 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import hashlib
|
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# 数据库初始化文件,删掉arcaea_database.db文件后运行即可,谨慎使用
|
# 数据库初始化文件,删掉arcaea_database.db文件后运行即可,谨慎使用
|
||||||
|
|
||||||
conn = sqlite3.connect('arcaea_database.db')
|
ARCAEA_SERVER_VERSION = 'v2.9.0'
|
||||||
c = conn.cursor()
|
|
||||||
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
|
|
||||||
);''')
|
|
||||||
c.execute('''create table if not exists login(access_token text,
|
|
||||||
user_id int,
|
|
||||||
last_login_time int,
|
|
||||||
last_login_ip text,
|
|
||||||
last_login_device text
|
|
||||||
);''')
|
|
||||||
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,
|
|
||||||
level_exp real,
|
|
||||||
frag real,
|
|
||||||
prog real,
|
|
||||||
overdrive real,
|
|
||||||
skill_id text,
|
|
||||||
skill_unlock_level int,
|
|
||||||
skill_requires_uncap int,
|
|
||||||
skill_id_uncap text,
|
|
||||||
char_type int,
|
|
||||||
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,
|
|
||||||
level int,
|
|
||||||
exp real,
|
|
||||||
level_exp real,
|
|
||||||
frag real,
|
|
||||||
prog real,
|
|
||||||
overdrive real,
|
|
||||||
skill_id text,
|
|
||||||
skill_unlock_level int,
|
|
||||||
skill_requires_uncap int,
|
|
||||||
skill_id_uncap text,
|
|
||||||
char_type int,
|
|
||||||
uncap_cores text,
|
|
||||||
is_uncapped int,
|
|
||||||
is_uncapped_override int
|
|
||||||
);''')
|
|
||||||
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,
|
|
||||||
price int,
|
|
||||||
orig_price int,
|
|
||||||
discount_from int,
|
|
||||||
discount_to 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,
|
|
||||||
primary key(user_id, 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
|
|
||||||
);''')
|
|
||||||
c.execute('''create table if not exists present(present_id text primary key,
|
|
||||||
expire_ts int,
|
|
||||||
items text,
|
|
||||||
description text
|
|
||||||
);''')
|
|
||||||
c.execute('''create table if not exists user_present(user_id int,
|
|
||||||
present_id text,
|
|
||||||
primary key(user_id, present_id)
|
|
||||||
);''')
|
|
||||||
|
|
||||||
|
|
||||||
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',
|
def main(path='./'):
|
||||||
'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']
|
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,
|
||||||
|
world_mode_locked_end_ts 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 songplay_token(token text primary key,
|
||||||
|
user_id int,
|
||||||
|
song_id text,
|
||||||
|
difficulty int,
|
||||||
|
course_id text,
|
||||||
|
course_state int,
|
||||||
|
course_score int,
|
||||||
|
course_clear_type int,
|
||||||
|
stamina_multiply int,
|
||||||
|
fragment_multiply int,
|
||||||
|
prog_boost_multiply int
|
||||||
|
);''')
|
||||||
|
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,
|
||||||
|
discount_reason text
|
||||||
|
);''')
|
||||||
|
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,
|
||||||
|
finalestate_data text
|
||||||
|
);''')
|
||||||
|
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 chart(song_id text primary key,
|
||||||
|
name text,
|
||||||
|
rating_pst int,
|
||||||
|
rating_prs int,
|
||||||
|
rating_ftr int,
|
||||||
|
rating_byn int
|
||||||
|
);''')
|
||||||
|
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)
|
||||||
|
);''')
|
||||||
|
|
||||||
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',
|
c.execute('''create table if not exists role(role_id int primary key,
|
||||||
'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']
|
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)
|
||||||
|
);''')
|
||||||
|
c.execute('''create table if not exists course(course_id text primary key,
|
||||||
|
course_name text,
|
||||||
|
dan_name text,
|
||||||
|
style int,
|
||||||
|
gauge_requirement text,
|
||||||
|
flag_as_hidden_when_requirements_not_met int,
|
||||||
|
can_start int
|
||||||
|
);''')
|
||||||
|
c.execute('''create table if not exists user_course(user_id int,
|
||||||
|
course_id text,
|
||||||
|
high_score int,
|
||||||
|
best_clear_type int,
|
||||||
|
primary key(user_id, course_id)
|
||||||
|
);''')
|
||||||
|
c.execute('''create table if not exists course_chart(course_id text,
|
||||||
|
song_id text,
|
||||||
|
difficulty int,
|
||||||
|
flag_as_hidden int,
|
||||||
|
song_index int,
|
||||||
|
primary key(course_id, song_id, difficulty)
|
||||||
|
);''')
|
||||||
|
c.execute('''create table if not exists course_requirement(course_id text,
|
||||||
|
required_id text,
|
||||||
|
primary key(course_id, required_id)
|
||||||
|
);''')
|
||||||
|
c.execute('''create table if not exists course_item(course_id text,
|
||||||
|
item_id text,
|
||||||
|
type text,
|
||||||
|
amount int,
|
||||||
|
primary key(course_id, item_id, type)
|
||||||
|
);''')
|
||||||
|
|
||||||
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', '', '', 'shirabe_entry_fee',
|
# 搭档初始化
|
||||||
'', '', '', '', '', '', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
|
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', 'vita', 'hikari(fatalis)']
|
||||||
|
|
||||||
for i in range(0, 40):
|
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',
|
||||||
if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21]:
|
'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_vita', 'skill_fatalis']
|
||||||
sql = 'insert into character values('+str(
|
|
||||||
i)+',"'+char[i]+'''",30,25000,25000,90,90,90,"'''+skill_id[i]+'''",0,0,"'''+skill_id_uncap[i]+'''",0,'',1,1)'''
|
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
|
||||||
c.execute(sql)
|
'', '', '', '', '', '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, 0, 0]
|
||||||
|
|
||||||
|
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, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27]
|
||||||
|
|
||||||
|
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, 50, 60]
|
||||||
|
|
||||||
|
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, 38, 30, 49, 15, 34, 45, 45, 38, 67, 65]
|
||||||
|
|
||||||
|
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, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50]
|
||||||
|
|
||||||
|
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, 79, 55, 65, 59, 90, 50, 90, 90, 75, 100]
|
||||||
|
|
||||||
|
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, 61, 40, 69, 62, 51, 90, 67, 60, 100, 105]
|
||||||
|
|
||||||
|
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, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50]
|
||||||
|
|
||||||
|
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, 79, 55, 65, 59, 90, 50, 90, 90, 75, 100]
|
||||||
|
|
||||||
|
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, 61, 40, 69, 62, 51, 90, 67, 60, 100, 105]
|
||||||
|
|
||||||
|
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, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3]
|
||||||
|
|
||||||
|
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, 56):
|
||||||
|
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:
|
else:
|
||||||
if i != 5:
|
if i == 5:
|
||||||
sql = 'insert into character values('+str(
|
continue
|
||||||
i)+',"'+char[i]+'''",30,25000,25000,90,90,90,"'''+skill_id[i]+'''",0,0,"'''+skill_id_uncap[i]+'''",0,'',0,0)'''
|
sql = '''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)'''
|
||||||
c.execute(sql)
|
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))
|
||||||
|
|
||||||
def b2int(x):
|
for i in char_core:
|
||||||
# int与布尔值转换
|
for j in char_core[i]:
|
||||||
if x:
|
c.execute('''insert into char_item values(?,?,'core',?)''',
|
||||||
return 1
|
(i, j['core_id'], j['amount']))
|
||||||
else:
|
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
|
||||||
return 0
|
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase']
|
||||||
|
|
||||||
|
for i in cores:
|
||||||
|
c.execute('''insert into item values(?,"core",1,'')''', (i,))
|
||||||
|
|
||||||
def insert_items(c, items):
|
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", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia"]
|
||||||
|
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", "scenery_chap6", "scenery_chap7"]
|
||||||
|
for i in world_unlocks:
|
||||||
|
c.execute('''insert into item values(?,"world_unlock",1,'')''', (i,))
|
||||||
|
|
||||||
|
course_banners = ['course_banner_' + str(i) for i in range(1, 12)]
|
||||||
|
for i in course_banners:
|
||||||
|
c.execute('''insert into item values(?,"course_banner",1,'')''', (i,))
|
||||||
|
|
||||||
|
c.execute('''insert into item values(?,?,?,?)''',
|
||||||
|
('fragment', 'fragment', 1, ''))
|
||||||
|
c.execute('''insert into item values(?,?,?,?)''',
|
||||||
|
('memory', 'memory', 1, ''))
|
||||||
|
c.execute('''insert into item values(?,?,?,?)''',
|
||||||
|
('anni5tix', 'anni5tix', 1, ''))
|
||||||
|
|
||||||
|
def insert_items(c, items):
|
||||||
# 物品数据导入
|
# 物品数据导入
|
||||||
for i in items:
|
for i in items:
|
||||||
if 'discount_from' not in i:
|
if 'discount_from' not in i:
|
||||||
@@ -259,60 +458,118 @@ def insert_items(c, items):
|
|||||||
discount_to = -1
|
discount_to = -1
|
||||||
else:
|
else:
|
||||||
discount_to = i['discount_to']
|
discount_to = i['discount_to']
|
||||||
|
if 'discount_reason' not in i:
|
||||||
|
discount_reason = ''
|
||||||
|
else:
|
||||||
|
discount_reason = i['discount_reason']
|
||||||
|
c.execute('''insert into purchase values(?,?,?,?,?,?)''',
|
||||||
|
(i['name'], i['price'], i['orig_price'], discount_from, discount_to, discount_reason))
|
||||||
for j in i['items']:
|
for j in i['items']:
|
||||||
if "_id" not in j:
|
if "_id" not in j:
|
||||||
_id = ''
|
_id = ''
|
||||||
else:
|
else:
|
||||||
_id = j['_id']
|
_id = j['_id']
|
||||||
if j['type'] != 'character':
|
c.execute(
|
||||||
c.execute('''insert into item(item_id, type, is_available, price, orig_price, discount_from, discount_to, _id) values(:a,:b,:c,:d,:e,:f,:g,:h)''', {
|
'''select exists(select * from item where item_id=?)''', (j['id'],))
|
||||||
'a': j['id'], 'b': j['type'], 'c': b2int(j['is_available']), 'd': i['price'], 'e': i['orig_price'], 'f': discount_from, 'g': discount_to, 'h': _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初始化
|
||||||
|
try:
|
||||||
|
f = open(path+'singles.json', 'r')
|
||||||
|
singles = json.load(f)
|
||||||
|
f.close()
|
||||||
|
insert_items(c, singles)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
f = open('singles.json', 'r')
|
try:
|
||||||
singles = json.load(f)
|
f = open(path+'packs.json', 'r')
|
||||||
f.close()
|
packs = json.load(f)
|
||||||
insert_items(c, singles)
|
f.close()
|
||||||
|
insert_items(c, packs)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
f = open('packs.json', 'r')
|
# course初始化
|
||||||
packs = json.load(f)
|
try:
|
||||||
f.close()
|
f = open(path+'courses.json', 'r', encoding="utf-8")
|
||||||
insert_items(c, packs)
|
courses = json.load(f)
|
||||||
|
f.close()
|
||||||
|
except:
|
||||||
|
courses = []
|
||||||
|
|
||||||
conn.commit()
|
if courses:
|
||||||
conn.close()
|
if path == './':
|
||||||
|
# 有什么好办法吗
|
||||||
|
import sys
|
||||||
|
sys.path.append('..')
|
||||||
|
from core.course import Course
|
||||||
|
for i in courses:
|
||||||
|
x = Course(c).from_dict(i)
|
||||||
|
x.insert_all()
|
||||||
|
|
||||||
|
# api权限与权限组初始化
|
||||||
|
role = ['admin', 'user', 'selecter']
|
||||||
|
role_caption = ['管理员', '用户', '查询接口']
|
||||||
|
|
||||||
def arc_register(name: str, password: str):
|
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):
|
def build_user_code(c):
|
||||||
return '123456789'
|
return '123456789'
|
||||||
|
|
||||||
def build_user_id(c):
|
def build_user_id(c):
|
||||||
return 2000000
|
return 2000000
|
||||||
|
|
||||||
# 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, 21]:
|
|
||||||
# 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)
|
|
||||||
def insert_user_char(c, user_id):
|
def insert_user_char(c, user_id):
|
||||||
# 为用户添加所有可用角色
|
# 为用户添加可用角色
|
||||||
c.execute('''select * from character''')
|
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
|
||||||
x = c.fetchall()
|
(user_id, 0, 30, 25000, 1, 0))
|
||||||
if x != []:
|
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
|
||||||
for i in x:
|
(user_id, 1, 30, 25000, 1, 0))
|
||||||
c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', {
|
|
||||||
'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]})
|
|
||||||
|
|
||||||
conn = sqlite3.connect('arcaea_database.db')
|
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()
|
c = conn.cursor()
|
||||||
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
|
hash_pwd = '41e5653fc7aeb894026d6bb7b2db7f65902b454945fa8fd65a6327047b5277fb'
|
||||||
c.execute(
|
c.execute(
|
||||||
'''select exists(select * from user where name = :name)''', {'name': name})
|
'''select exists(select * from user where name = :name)''', {'name': name})
|
||||||
if c.fetchone() == (0,):
|
if c.fetchone() == (0,):
|
||||||
@@ -336,5 +593,8 @@ def arc_register(name: str, password: str):
|
|||||||
conn.close()
|
conn.close()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
arc_register('admin')
|
||||||
|
|
||||||
arc_register('admin', 'admin123')
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|||||||
376
latest version/database/map/byd_bookmaker.json
Normal file
376
latest version/database/map/byd_bookmaker.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
78
latest version/database/map/byd_lostcivilization.json
Normal file
78
latest version/database/map/byd_lostcivilization.json
Normal 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"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
114
latest version/database/map/test3.json
Normal file
114
latest version/database/map/test3.json
Normal 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
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
@@ -1,218 +1,608 @@
|
|||||||
[{
|
[
|
||||||
|
{
|
||||||
"name": "core",
|
"name": "core",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "core",
|
"id": "core",
|
||||||
"_id": "5fd01460a6e343038b057d28",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1615248000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1615852799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "shiawase",
|
"name": "shiawase",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "shiawase",
|
"id": "shiawase",
|
||||||
"_id": "5fd01461a6e343038b057d65",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"type": "character",
|
"type": "character",
|
||||||
"id": "kou",
|
"id": "kou",
|
||||||
"_id": "5fd01461a6e343038b057d64",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1552089600000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1552694399000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "dynamix",
|
"name": "dynamix",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "dynamix",
|
"id": "dynamix",
|
||||||
"_id": "5fd01461a6e343038b057d6f",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"type": "character",
|
"type": "character",
|
||||||
"id": "sapphire",
|
"id": "sapphire",
|
||||||
"_id": "5fd01461a6e343038b057d6e",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "mirai",
|
"name": "mirai",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "mirai",
|
"id": "mirai",
|
||||||
"_id": "5fd01461a6e343038b057d69",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"type": "character",
|
"type": "character",
|
||||||
"id": "lethe",
|
"id": "lethe",
|
||||||
"_id": "5fd01461a6e343038b057d68",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1552089600000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1552694399000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "yugamu",
|
"name": "yugamu",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "yugamu",
|
"id": "yugamu",
|
||||||
"_id": "5fd01460a6e343038b057d29",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "lanota",
|
"name": "lanota",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "lanota",
|
"id": "lanota",
|
||||||
"_id": "5fd01460a6e343038b057d40",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "nijuusei",
|
"name": "nijuusei",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "nijuusei",
|
"id": "nijuusei",
|
||||||
"_id": "5fd01460a6e343038b057d2a",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1615248000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1615852799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "rei",
|
"name": "rei",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "rei",
|
"id": "rei",
|
||||||
"_id": "5fd01460a6e343038b057d4d",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "tonesphere",
|
"name": "tonesphere",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "tonesphere",
|
"id": "tonesphere",
|
||||||
"_id": "5fd01460a6e343038b057d42",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "groovecoaster",
|
"name": "groovecoaster",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "groovecoaster",
|
"id": "groovecoaster",
|
||||||
"_id": "5fd01460a6e343038b057d5b",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "zettai",
|
"name": "zettai",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "zettai",
|
"id": "zettai",
|
||||||
"_id": "5fd01460a6e343038b057d4f",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"discount_from": 1583712000000,
|
"discount_from": 1646784000000,
|
||||||
"discount_to": 1584316799000
|
"discount_to": 1647388799000
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "chunithm",
|
"name": "chunithm",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "chunithm",
|
"id": "chunithm",
|
||||||
"_id": "5fd01460a6e343038b057d39",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 3,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 300,
|
"price": 300,
|
||||||
"orig_price": 300
|
"orig_price": 300
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"name": "prelude",
|
"name": "prelude",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "prelude",
|
"id": "prelude",
|
||||||
"_id": "5fd01460a6e343038b057d5c",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 4,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 400,
|
"price": 400,
|
||||||
"orig_price": 400
|
"orig_price": 400,
|
||||||
}, {
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "omatsuri",
|
"name": "omatsuri",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "omatsuri",
|
"id": "omatsuri",
|
||||||
"_id": "5fd01460a6e343038b057d50",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500
|
"orig_price": 500,
|
||||||
}, {
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "vs",
|
"name": "vs",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "vs",
|
"id": "vs",
|
||||||
"_id": "5fd01460a6e343038b057d3c",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"price": 500,
|
"price": 500,
|
||||||
"orig_price": 500
|
"orig_price": 500,
|
||||||
}, {
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "extend",
|
"name": "extend",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "extend",
|
"id": "extend",
|
||||||
"_id": "5fd01460a6e343038b057d5f",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
"price": 700,
|
{
|
||||||
"orig_price": 700
|
"type": "core",
|
||||||
}, {
|
"amount": 7,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"price": 0,
|
||||||
|
"orig_price": 700,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000,
|
||||||
|
"discount_reason": "extend"
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "alice",
|
"name": "alice",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "alice",
|
"id": "alice",
|
||||||
"_id": "5fd01460a6e343038b057d55",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"orig_price": 500,
|
"orig_price": 500,
|
||||||
"price": 500
|
"price": 500,
|
||||||
}, {
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "alice_append_1",
|
"name": "alice_append_1",
|
||||||
"items": [{
|
"items": [
|
||||||
|
{
|
||||||
"type": "pack",
|
"type": "pack",
|
||||||
"id": "alice_append_1",
|
"id": "alice_append_1",
|
||||||
"_id": "5fd01460a6e343038b057d33",
|
|
||||||
"is_available": true
|
"is_available": true
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 3,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 300,
|
||||||
|
"price": 300,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
"orig_price": 300,
|
||||||
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "musedash",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "pack",
|
||||||
|
"id": "musedash",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 4,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 400,
|
||||||
|
"price": 400,
|
||||||
|
"discount_from": 1646784000000,
|
||||||
|
"discount_to": 1647388799000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lanota_append_1",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "pack",
|
||||||
|
"id": "lanota_append_1",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 3,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 300,
|
||||||
|
"price": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "finale",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "pack",
|
||||||
|
"id": "finale",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 500,
|
||||||
|
"price": 500
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
0
latest version/log/error.log
Normal file
0
latest version/log/error.log
Normal file
@@ -1,69 +1,35 @@
|
|||||||
from flask import Flask, request, jsonify, make_response, send_from_directory
|
# encoding: utf-8
|
||||||
import configparser
|
|
||||||
import base64
|
|
||||||
import server.auth
|
|
||||||
import server.info
|
|
||||||
import server.setme
|
|
||||||
import server.arcscore
|
|
||||||
import web.login
|
|
||||||
import web.index
|
|
||||||
import server.arcworld
|
|
||||||
import server.arcdownload
|
|
||||||
import server.arcpurchase
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from logging.config import dictConfig
|
||||||
|
from multiprocessing import Process, set_start_method
|
||||||
|
|
||||||
|
from flask import Flask, request, send_from_directory
|
||||||
|
|
||||||
|
import api
|
||||||
|
import server
|
||||||
|
import server.init
|
||||||
|
import web.index
|
||||||
|
import web.login
|
||||||
|
from core.constant import Constant
|
||||||
|
from core.download import UserDownload, initialize_songfile
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.sql import Connect
|
||||||
|
from server.func import error_return
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
wsgi_app = app.wsgi_app
|
wsgi_app = app.wsgi_app
|
||||||
|
|
||||||
|
os.chdir(sys.path[0]) # 更改工作路径,以便于愉快使用相对路径
|
||||||
|
|
||||||
def error_return(error_code): # 错误返回
|
app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
|
||||||
# -7 处理交易时发生了错误
|
app.config['SESSION_TYPE'] = 'filesystem'
|
||||||
# -5 所有的曲目都已经下载完毕
|
app.register_blueprint(web.login.bp)
|
||||||
# -4 您的账号已在别处登录
|
app.register_blueprint(web.index.bp)
|
||||||
# -3 无法连接至服务器
|
app.register_blueprint(api.bp)
|
||||||
# 2 Arcaea服务器正在维护
|
app.register_blueprint(server.bp)
|
||||||
# 5 请更新Arcaea到最新版本
|
|
||||||
# 100 无法在此ip地址下登录游戏
|
|
||||||
# 101 用户名占用
|
|
||||||
# 102 电子邮箱已注册
|
|
||||||
# 103 已有一个账号由此设备创建
|
|
||||||
# 104 用户名密码错误
|
|
||||||
# 105 24小时内登入两台设备
|
|
||||||
# 106 账户冻结
|
|
||||||
# 107 你没有足够的体力
|
|
||||||
# 113 活动已结束
|
|
||||||
# 114 该活动已结束,您的成绩不会提交
|
|
||||||
# 120 封号警告
|
|
||||||
# 121 账户冻结
|
|
||||||
# 122 账户暂时冻结
|
|
||||||
# 123 账户被限制
|
|
||||||
# 124 你今天不能再使用这个IP地址创建新的账号
|
|
||||||
# 150 非常抱歉您已被限制使用此功能
|
|
||||||
# 151 目前无法使用此功能
|
|
||||||
# 401 用户不存在
|
|
||||||
# 403 无法连接至服务器
|
|
||||||
# 501 502 -6 此物品目前无法获取
|
|
||||||
# 504 无效的序列码
|
|
||||||
# 505 此序列码已被使用
|
|
||||||
# 506 你已拥有了此物品
|
|
||||||
# 601 好友列表已满
|
|
||||||
# 602 此用户已是好友
|
|
||||||
# 604 你不能加自己为好友
|
|
||||||
# 903 下载量超过了限制,请24小时后重试
|
|
||||||
# 905 请在再次使用此功能前等待24小时
|
|
||||||
# 1001 设备数量达到上限
|
|
||||||
# 1002 此设备已使用过此功能
|
|
||||||
# 9801 下载歌曲时发生问题,请再试一次
|
|
||||||
# 9802 保存歌曲时发生问题,请检查设备空间容量
|
|
||||||
# 9905 没有在云端发现任何数据
|
|
||||||
# 9907 更新数据时发生了问题
|
|
||||||
# 9908 服务器只支持最新的版本,请更新Arcaea
|
|
||||||
# 其它 发生未知错误
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error_code": error_code
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@@ -80,597 +46,101 @@ def favicon():
|
|||||||
return app.send_static_file('favicon.ico')
|
return app.send_static_file('favicon.ico')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/auth/login', methods=['POST']) # 登录接口
|
|
||||||
def login():
|
|
||||||
headers = request.headers
|
|
||||||
id_pwd = headers['Authorization']
|
|
||||||
id_pwd = base64.b64decode(id_pwd[6:]).decode()
|
|
||||||
name, password = id_pwd.split(':', 1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
token = server.auth.arc_login(name, password)
|
|
||||||
if token is not None:
|
|
||||||
r = {"success": True, "token_type": "Bearer"}
|
|
||||||
r['access_token'] = token
|
|
||||||
return jsonify(r)
|
|
||||||
else:
|
|
||||||
return error_return(104) # 用户名或密码错误
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/user/', methods=['POST']) # 注册接口
|
|
||||||
def register():
|
|
||||||
name = request.form['name']
|
|
||||||
password = request.form['password']
|
|
||||||
try:
|
|
||||||
user_id, token, error_code = server.auth.arc_register(name, password)
|
|
||||||
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) # 应该是101,用户名被占用,毕竟电子邮箱、设备号没记录
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
# 集成式请求,没想到什么好办法处理,就先这样写着
|
|
||||||
@app.route('/coffee/12/compose/aggregate', methods=['GET'])
|
|
||||||
def aggregate():
|
|
||||||
calls = request.args.get('calls')
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/user/me/character', methods=['POST']) # 角色切换
|
|
||||||
def character_change():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
character_id = request.form['character']
|
|
||||||
skill_sealed = request.form['skill_sealed']
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/<path:path>/toggle_uncap', methods=['POST']) # 角色觉醒切换
|
|
||||||
def character_uncap(path):
|
|
||||||
character_id = int(path[22:])
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/friend/me/add', methods=['POST']) # 加好友
|
|
||||||
def add_friend():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
friend_code = request.form['friend_code']
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
friend_id = server.auth.code_get_id(friend_code)
|
|
||||||
if user_id is not None and 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:
|
|
||||||
if friend_id is None:
|
|
||||||
return error_return(401)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/friend/me/delete', methods=['POST']) # 删好友
|
|
||||||
def delete_friend():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
friend_id = int(request.form['friend_id'])
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None and 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:
|
|
||||||
if friend_id is None:
|
|
||||||
return error_return(401)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/score/song/friend', methods=['GET']) # 好友排名,默认最多50
|
|
||||||
def song_score_friend():
|
|
||||||
song_id = request.args.get('song_id')
|
|
||||||
difficulty = request.args.get('difficulty')
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/score/song/me', methods=['GET']) # 我的排名,默认最多20
|
|
||||||
def song_score_me():
|
|
||||||
song_id = request.args.get('song_id')
|
|
||||||
difficulty = request.args.get('difficulty')
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/score/song', methods=['GET']) # TOP20
|
|
||||||
def song_score_top():
|
|
||||||
song_id = request.args.get('song_id')
|
|
||||||
difficulty = request.args.get('difficulty')
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/score/song', methods=['POST']) # 成绩上传
|
|
||||||
def song_score_post():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
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']
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
# 增加成绩校验
|
|
||||||
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 r:
|
|
||||||
if re:
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": re
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": {"user_rating": r}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/score/token', methods=['GET']) # 成绩上传所需的token,显然我不想验证
|
|
||||||
def score_token():
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": {
|
|
||||||
"token": "1145141919810"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# 世界模式成绩上传所需的token,无验证
|
|
||||||
@app.route('/coffee/12/score/token/world', methods=['GET'])
|
|
||||||
def score_token_world():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
args = request.args
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id:
|
|
||||||
server.arcworld.play_world_song(user_id, args)
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": {
|
|
||||||
"stamina": 12,
|
|
||||||
"max_stamina_ts": 1599547603825,
|
|
||||||
"token": "13145201919810"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/user/me/save', methods=['GET']) # 从云端同步
|
|
||||||
def cloud_get():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
r = server.arcscore.arc_all_get(user_id)
|
|
||||||
if r is not None:
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": r
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/user/me/save', methods=['POST']) # 向云端同步
|
|
||||||
def cloud_post():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
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']
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/purchase/me/redeem', methods=['POST']) # 兑换码,依然没有用
|
|
||||||
def redeem():
|
|
||||||
return error_return(504)
|
|
||||||
|
|
||||||
|
|
||||||
# 礼物确认
|
|
||||||
@app.route('/coffee/12/present/me/claim/<present_id>', methods=['POST'])
|
|
||||||
def claim_present(present_id):
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
flag = server.arcpurchase.claim_user_present(user_id, present_id)
|
|
||||||
if flag:
|
|
||||||
return jsonify({
|
|
||||||
"success": True
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
# 购买,为了world模式boost一下
|
|
||||||
@app.route('/coffee/12/purchase/me/item', methods=['POST'])
|
|
||||||
def item():
|
|
||||||
return jsonify({
|
|
||||||
"success": True
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/purchase/me/pack', methods=['POST']) # 曲包和单曲购买
|
|
||||||
def pack():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id:
|
|
||||||
if 'pack_id' in request.form:
|
|
||||||
return jsonify(server.arcpurchase.buy_pack(user_id, request.form['pack_id']))
|
|
||||||
if 'single_id' in request.form:
|
|
||||||
return jsonify(server.arcpurchase.buy_single(user_id, request.form['single_id']))
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
return jsonify({
|
|
||||||
"success": True
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/purchase/bundle/single', methods=['GET']) # 单曲购买信息获取
|
|
||||||
def single():
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": server.arcpurchase.get_single_purchase()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/world/map/me', methods=['GET']) # 获得世界模式信息,所有地图
|
|
||||||
def world_all():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/world/map/me/', methods=['POST']) # 进入地图
|
|
||||||
def world_in():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
map_id = request.form['map_id']
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id:
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"value": server.arcworld.get_user_world(user_id, map_id)
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/world/map/me/<map_id>', methods=['GET']) # 获得单个地图完整信息
|
|
||||||
def world_one(map_id):
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_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)]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/12/serve/download/me/song', methods=['GET']) # 歌曲下载
|
|
||||||
def download_song():
|
|
||||||
headers = request.headers
|
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
song_ids = request.args.getlist('sid')
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
return error_return(108)
|
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/download/<path:file_path>', methods=['GET']) # 下载
|
@app.route('/download/<path:file_path>', methods=['GET']) # 下载
|
||||||
def download(file_path):
|
def download(file_path):
|
||||||
t = request.args.get('t')
|
with Connect() as c:
|
||||||
try:
|
try:
|
||||||
message = server.arcdownload.is_token_able_download(t)
|
x = UserDownload(c)
|
||||||
if message == 0:
|
x.file_path = file_path
|
||||||
path = os.path.join('./database/songs', file_path)
|
x.select_from_token(request.args.get('t'))
|
||||||
if os.path.isfile(path) and not('../' in path or '..\\' in path):
|
if x.is_limited:
|
||||||
return send_from_directory('./database/songs', file_path, as_attachment=True)
|
raise ArcError('You have reached the download limit.', 903)
|
||||||
else:
|
if x.is_valid:
|
||||||
return error_return(109)
|
x.insert_user_download()
|
||||||
else:
|
return send_from_directory(Constant.SONG_FILE_FOLDER_PATH, file_path, as_attachment=True)
|
||||||
return error_return(message)
|
except ArcError as e:
|
||||||
except:
|
return error_return(e)
|
||||||
return error_return(108)
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coffee/<path:path>', methods=['POST']) # 三个设置,写在最后降低优先级
|
def tcp_server_run():
|
||||||
def sys_set(path):
|
if Config.SSL_CERT and Config.SSL_KEY:
|
||||||
set_arg = path[10:]
|
app.run(Config.HOST, Config.PORT, ssl_context=(
|
||||||
headers = request.headers
|
Config.SSL_CERT, Config.SSL_KEY))
|
||||||
token = headers['Authorization']
|
|
||||||
token = token[7:]
|
|
||||||
value = request.form['value']
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_id = server.auth.token_get_id(token)
|
|
||||||
if user_id is not None:
|
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
return error_return(108)
|
app.run(Config.HOST, Config.PORT)
|
||||||
except:
|
|
||||||
return error_return(108)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
config = configparser.ConfigParser()
|
log_dict = {
|
||||||
path = r'setting.ini'
|
'version': 1,
|
||||||
config.read(path, encoding="utf-8")
|
'root': {
|
||||||
HOST = config.get('CONFIG', 'HOST')
|
'level': 'INFO',
|
||||||
PORT = config.get('CONFIG', 'PORT')
|
'handlers': ['wsgi', 'error_file']
|
||||||
app.config.from_mapping(SECRET_KEY='1145141919810')
|
},
|
||||||
app.register_blueprint(web.login.bp)
|
'handlers': {
|
||||||
app.register_blueprint(web.index.bp)
|
'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"
|
||||||
|
}
|
||||||
|
|
||||||
app.run(HOST, PORT)
|
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 song data...")
|
||||||
|
try:
|
||||||
|
initialize_songfile()
|
||||||
|
app.logger.info('Complete!')
|
||||||
|
except:
|
||||||
|
app.logger.warning('Initialization error!')
|
||||||
|
|
||||||
|
if Config.UDP_PORT and Config.UDP_PORT != '':
|
||||||
|
from server.multiplayer import conn2
|
||||||
|
from udpserver.udp_main import link_play
|
||||||
|
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...")
|
||||||
|
tcp_server_run()
|
||||||
|
[p.join() for p in process]
|
||||||
|
else:
|
||||||
|
tcp_server_run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
set_start_method("spawn")
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
latest version/requirements.txt
Normal file
2
latest version/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
flask>=2.0
|
||||||
|
cryptography
|
||||||
@@ -1 +1,3 @@
|
|||||||
python main.py
|
cd /d %~dp0
|
||||||
|
:: Develop server
|
||||||
|
python -B main.py
|
||||||
24
latest version/server/__init__.py
Normal file
24
latest version/server/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from setting import Config
|
||||||
|
from . import user
|
||||||
|
from . import auth
|
||||||
|
from . import friend
|
||||||
|
from . import score
|
||||||
|
from . import world
|
||||||
|
from . import purchase
|
||||||
|
from . import present
|
||||||
|
from . import others
|
||||||
|
from . import multiplayer
|
||||||
|
from . import course
|
||||||
|
|
||||||
|
bp = Blueprint('server', __name__, url_prefix=Config.GAME_API_PREFIX)
|
||||||
|
bp.register_blueprint(user.bp)
|
||||||
|
bp.register_blueprint(auth.bp)
|
||||||
|
bp.register_blueprint(friend.bp)
|
||||||
|
bp.register_blueprint(score.bp)
|
||||||
|
bp.register_blueprint(world.bp)
|
||||||
|
bp.register_blueprint(purchase.bp)
|
||||||
|
bp.register_blueprint(present.bp)
|
||||||
|
bp.register_blueprint(others.bp)
|
||||||
|
bp.register_blueprint(multiplayer.bp)
|
||||||
|
bp.register_blueprint(course.bp)
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import os
|
|
||||||
import hashlib
|
|
||||||
from flask import url_for
|
|
||||||
import sqlite3
|
|
||||||
import time
|
|
||||||
|
|
||||||
time_limit = 3000 # 每个玩家24小时下载次数限制
|
|
||||||
time_gap_limit = 1000 # 下载链接有效秒数
|
|
||||||
|
|
||||||
|
|
||||||
def get_file_md5(file_path):
|
|
||||||
# 计算文件MD5
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
return None
|
|
||||||
myhash = hashlib.md5()
|
|
||||||
f = open(file_path, 'rb')
|
|
||||||
while True:
|
|
||||||
b = f.read(8096)
|
|
||||||
if not b:
|
|
||||||
break
|
|
||||||
myhash.update(b)
|
|
||||||
f.close()
|
|
||||||
return myhash.hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def get_one_song(c, user_id, song_id, file_dir='./database/songs'):
|
|
||||||
# 获取一首歌的下载链接,返回字典
|
|
||||||
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()
|
|
||||||
token = token[:8]
|
|
||||||
|
|
||||||
if i == 'base.ogg':
|
|
||||||
re['audio'] = {"checksum": get_file_md5(os.path.join(file_dir, song_id, 'base.ogg')),
|
|
||||||
"url": url_for('download', file_path=song_id+'/base.ogg', t=token, _external=True)}
|
|
||||||
else:
|
|
||||||
if 'chart' not in re:
|
|
||||||
re['chart'] = {}
|
|
||||||
re['chart'][i[0]] = {"checksum": get_file_md5(os.path.join(file_dir, song_id, i)),
|
|
||||||
"url": url_for('download', file_path=song_id+'/'+i, t=token, _external=True)}
|
|
||||||
|
|
||||||
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'):
|
|
||||||
# 获取所有歌的下载链接,返回字典
|
|
||||||
dir_list = os.listdir(file_dir)
|
|
||||||
re = {}
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
for i in dir_list:
|
|
||||||
if os.path.isdir(os.path.join(file_dir, i)):
|
|
||||||
re.update(get_one_song(c, user_id, i))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_some_songs(user_id, song_ids):
|
|
||||||
# 获取一些歌的下载链接,返回字典
|
|
||||||
re = {}
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
for song_id in song_ids:
|
|
||||||
re.update(get_one_song(c, user_id, song_id))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def is_token_able_download(t):
|
|
||||||
# token是否可以下载,返回错误码,0即可以
|
|
||||||
errorcode = 0
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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})
|
|
||||||
else:
|
|
||||||
errorcode = 903
|
|
||||||
else:
|
|
||||||
errorcode = 108
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return errorcode
|
|
||||||
|
|
||||||
|
|
||||||
def is_able_download(user_id):
|
|
||||||
# 是否可以下载,返回布尔值
|
|
||||||
f = True
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
f = False
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return f
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
def int2b(x):
|
|
||||||
# int与布尔值转换
|
|
||||||
if x is None or x == 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_item(c, type='pack'):
|
|
||||||
# 读取packs内容,返回字典列表
|
|
||||||
c.execute('''select * from item where type = :a''', {'a': type})
|
|
||||||
x = c.fetchall()
|
|
||||||
if not x:
|
|
||||||
return []
|
|
||||||
|
|
||||||
re = []
|
|
||||||
for i in x:
|
|
||||||
r = {"name": i[0],
|
|
||||||
"items": [{
|
|
||||||
"type": i[1],
|
|
||||||
"id": i[0],
|
|
||||||
"is_available": int2b(i[2])
|
|
||||||
}],
|
|
||||||
"price": i[3],
|
|
||||||
"orig_price": i[4]}
|
|
||||||
|
|
||||||
if i[5] > 0:
|
|
||||||
r['discount_from'] = i[5]
|
|
||||||
if i[6] > 0:
|
|
||||||
r['discount_to'] = i[6]
|
|
||||||
|
|
||||||
re.append(r)
|
|
||||||
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_single_purchase():
|
|
||||||
# main里面没开数据库,这里写一下代替
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
re = get_item(c, type='single')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def buy_pack(user_id, pack_id):
|
|
||||||
# 曲包购买,返回字典
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select price from item where item_id = :a''', {'a': pack_id})
|
|
||||||
price = c.fetchone()
|
|
||||||
if price:
|
|
||||||
price = price[0]
|
|
||||||
else:
|
|
||||||
price = 0
|
|
||||||
|
|
||||||
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:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return {
|
|
||||||
"success": False
|
|
||||||
}
|
|
||||||
|
|
||||||
c.execute('''update user set ticket = :b where user_id = :a''',
|
|
||||||
{'a': user_id, 'b': ticket-price})
|
|
||||||
c.execute('''insert into user_item values(:a,:b,'pack')''',
|
|
||||||
{'a': user_id, 'b': pack_id})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return {
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def buy_single(user_id, single_id):
|
|
||||||
# 单曲购买,返回字典
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select price from item where item_id = :a''',
|
|
||||||
{'a': single_id})
|
|
||||||
price = c.fetchone()
|
|
||||||
if price:
|
|
||||||
price = price[0]
|
|
||||||
else:
|
|
||||||
price = 0
|
|
||||||
|
|
||||||
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:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return {
|
|
||||||
"success": False
|
|
||||||
}
|
|
||||||
|
|
||||||
c.execute('''update user set ticket = :b where user_id = :a''',
|
|
||||||
{'a': user_id, 'b': ticket-price})
|
|
||||||
c.execute('''insert into user_item values(:a,:b,'single')''',
|
|
||||||
{'a': user_id, 'b': single_id})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return {
|
|
||||||
"success": True
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 = []
|
|
||||||
if x:
|
|
||||||
for i in x:
|
|
||||||
re.append({'expire_ts': i[1],
|
|
||||||
'description': i[3],
|
|
||||||
'present_id': i[0],
|
|
||||||
'items': i[2]
|
|
||||||
})
|
|
||||||
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def claim_user_present(user_id, present_id):
|
|
||||||
# 确认并删除用户奖励,返回成功与否的布尔值
|
|
||||||
flag = False
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
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,):
|
|
||||||
flag = True
|
|
||||||
c.execute('''delete from user_present where user_id=:a and present_id=:b''',
|
|
||||||
{'a': user_id, 'b': present_id})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return flag
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,259 +0,0 @@
|
|||||||
import json
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def int2b(x):
|
|
||||||
# int与布尔值转换
|
|
||||||
if x is None or x == 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
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文件内容,返回字典
|
|
||||||
f = open('./database/map/'+map_id+'.json', 'r')
|
|
||||||
world_info = json.load(f)
|
|
||||||
f.close()
|
|
||||||
return world_info
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_world_info(user_id, map_id):
|
|
||||||
# 读取json文件内容,加上用户信息,返回字典
|
|
||||||
info = get_world_info(map_id)
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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:
|
|
||||||
c.execute('''insert into user_world values(:a,:b,0,0,0)''', {
|
|
||||||
'a': user_id, 'b': map_id})
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_map(user_id):
|
|
||||||
# 得到user的当前图,返回字符串
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select current_map from user where user_id = :a''',
|
|
||||||
{'a': user_id})
|
|
||||||
x = c.fetchone()
|
|
||||||
re = ''
|
|
||||||
if x:
|
|
||||||
re = x[0]
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_world_all(user_id):
|
|
||||||
# 读取所有地图信息并处理,返回字典列表
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
worlds = get_world_name()
|
|
||||||
re = []
|
|
||||||
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:
|
|
||||||
c.execute('''insert into user_world values(:a,:b,0,0,0)''', {
|
|
||||||
'a': user_id, 'b': map_id})
|
|
||||||
|
|
||||||
re.append(info)
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_world(user_id, map_id):
|
|
||||||
# 获取用户图信息,返回字典
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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": False,
|
|
||||||
"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,0)''', {
|
|
||||||
'a': user_id, 'b': map_id})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def change_user_current_map(user_id, map_id):
|
|
||||||
# 改变用户当前图
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''update user set current_map = :a where user_id=:b''', {
|
|
||||||
'a': map_id, 'b': user_id})
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def play_world_song(user_id, args):
|
|
||||||
# 声明是世界模式的打歌,并且记录加成信息
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
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:
|
|
||||||
prog_boost_multiply = int(args['prog_boost_multiply'])
|
|
||||||
|
|
||||||
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})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,138 +1,68 @@
|
|||||||
import sqlite3
|
import base64
|
||||||
import hashlib
|
import functools
|
||||||
import time
|
|
||||||
import server.arcworld
|
from core.error import ArcError, NoAccess
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserAuth, UserLogin
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from .func import error_return
|
||||||
|
|
||||||
|
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||||
|
|
||||||
|
|
||||||
def arc_login(name: str, password: str) -> str: # 登录判断
|
@bp.route('/login', methods=['POST']) # 登录接口
|
||||||
# 查询数据库中的user表,验证账号密码,返回并记录token
|
def login():
|
||||||
# token采用user_id和时间戳连接后hash生成(真的是瞎想的,没用bear)
|
if 'AppVersion' in request.headers: # 版本检查
|
||||||
# 密码和token的加密方式为 SHA-256
|
if Config.ALLOW_APPVERSION:
|
||||||
|
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
|
||||||
|
return error_return(NoAccess('Wrong app version.', 1203))
|
||||||
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
headers = request.headers
|
||||||
c = conn.cursor()
|
request.form['grant_type']
|
||||||
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
|
with Connect() as c:
|
||||||
c.execute('''select user_id from user where name = :name and password = :password''', {
|
try:
|
||||||
'name': name, 'password': hash_pwd})
|
id_pwd = headers['Authorization']
|
||||||
x = c.fetchone()
|
id_pwd = base64.b64decode(id_pwd[6:]).decode()
|
||||||
if x is not None:
|
name, password = id_pwd.split(':', 1)
|
||||||
user_id = str(x[0])
|
if 'DeviceId' in headers:
|
||||||
now = int(time.time() * 1000)
|
device_id = headers['DeviceId']
|
||||||
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:
|
else:
|
||||||
return 2000001
|
device_id = 'low_version'
|
||||||
|
|
||||||
def insert_user_char(c, user_id):
|
user = UserLogin(c)
|
||||||
# 为用户添加所有可用角色
|
user.login(name, password, device_id, request.remote_addr)
|
||||||
c.execute('''select * from character''')
|
|
||||||
x = c.fetchall()
|
|
||||||
if x != []:
|
|
||||||
for i in x:
|
|
||||||
c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', {
|
|
||||||
'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]})
|
|
||||||
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
|
||||||
c = conn.cursor()
|
except ArcError as e:
|
||||||
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
|
return error_return(e)
|
||||||
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, 0, 0, 0, 0, 0, 0, -1, 0, '', 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(
|
return error_return()
|
||||||
(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):
|
def auth_required(request):
|
||||||
# 用token获取id,没有考虑不同用户token相同情况,说不定会有bug
|
# arcaea登录验证,写成了修饰器
|
||||||
|
def decorator(view):
|
||||||
|
@functools.wraps(view)
|
||||||
|
def wrapped_view(*args, **kwargs):
|
||||||
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
headers = request.headers
|
||||||
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
|
|
||||||
|
|
||||||
|
if 'AppVersion' in headers: # 版本检查
|
||||||
|
if Config.ALLOW_APPVERSION:
|
||||||
|
if headers['AppVersion'] not in Config.ALLOW_APPVERSION:
|
||||||
|
return error_return(NoAccess('Wrong app version.', 1203))
|
||||||
|
|
||||||
def code_get_id(user_code):
|
with Connect() as c:
|
||||||
# 用user_code获取id
|
try:
|
||||||
|
user = UserAuth(c)
|
||||||
|
user.token = headers['Authorization'][7:]
|
||||||
|
return view(user.token_get_id(), *args, **kwargs)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
return error_return()
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select user_id from user where user_code = :a''',
|
return wrapped_view
|
||||||
{'a': user_code})
|
return decorator
|
||||||
x = c.fetchone()
|
|
||||||
if x is not None:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return x[0]
|
|
||||||
else:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|||||||
33
latest version/server/course.py
Normal file
33
latest version/server/course.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from core.constant import Constant
|
||||||
|
from core.course import UserCourseList
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.item import ItemCore
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserOnline
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('course', __name__, url_prefix='/course')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me', methods=['GET'])
|
||||||
|
@auth_required(request)
|
||||||
|
def course_me(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
core = ItemCore(c)
|
||||||
|
core.item_id = 'core_course_skip_purchase'
|
||||||
|
core.select(user)
|
||||||
|
x = UserCourseList(c, user)
|
||||||
|
x.select_all()
|
||||||
|
return success_return({
|
||||||
|
'courses': x.to_dict_list(),
|
||||||
|
"stamina_cost": Constant.COURSE_STAMINA_COST,
|
||||||
|
"course_skip_purchase_ticket": core.amount
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
49
latest version/server/friend.py
Normal file
49
latest version/server/friend.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.user import UserOnline, code_get_id
|
||||||
|
from .func import error_return, success_return
|
||||||
|
from .auth import auth_required
|
||||||
|
|
||||||
|
bp = Blueprint('friend', __name__, url_prefix='/friend')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/add', methods=['POST']) # 加好友
|
||||||
|
@auth_required(request)
|
||||||
|
def add_friend(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
friend_code = request.form['friend_code']
|
||||||
|
friend_id = code_get_id(c, friend_code)
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user.add_friend(friend_id)
|
||||||
|
|
||||||
|
return success_return({
|
||||||
|
"user_id": user.user_id,
|
||||||
|
"updatedAt": "2020-09-07T07:32:12.740Z",
|
||||||
|
"createdAt": "2020-09-06T10:05:18.471Z",
|
||||||
|
"friends": user.friends
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/delete', methods=['POST']) # 删好友
|
||||||
|
@auth_required(request)
|
||||||
|
def delete_friend(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
friend_id = int(request.form['friend_id'])
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user.delete_friend(friend_id)
|
||||||
|
|
||||||
|
return success_return({
|
||||||
|
"user_id": user.user_id,
|
||||||
|
"updatedAt": "2020-09-07T07:32:12.740Z",
|
||||||
|
"createdAt": "2020-09-06T10:05:18.471Z",
|
||||||
|
"friends": user.friends
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
66
latest version/server/func.py
Normal file
66
latest version/server/func.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
from core.error import ArcError
|
||||||
|
|
||||||
|
default_error = ArcError('Unknown Error')
|
||||||
|
|
||||||
|
|
||||||
|
def error_return(e: ArcError = default_error): # 错误返回
|
||||||
|
# -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
|
||||||
|
# 其它 发生未知错误
|
||||||
|
r = {"success": False, "error_code": e.error_code}
|
||||||
|
if e.extra_data:
|
||||||
|
r['extra'] = e.extra_data
|
||||||
|
|
||||||
|
return jsonify(r)
|
||||||
|
|
||||||
|
|
||||||
|
def success_return(value=None):
|
||||||
|
r = {"success": True}
|
||||||
|
if value is not None:
|
||||||
|
r['value'] = value
|
||||||
|
return jsonify(r)
|
||||||
@@ -1,346 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
import server.arcworld
|
|
||||||
import server.arcpurchase
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
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_character(c, user_id):
|
|
||||||
# 得到用户拥有的角色列表,返回列表
|
|
||||||
c.execute('''select * from user_char where user_id = :user_id''',
|
|
||||||
{'user_id': user_id})
|
|
||||||
x = c.fetchall()
|
|
||||||
if x != []:
|
|
||||||
s = []
|
|
||||||
for i in x:
|
|
||||||
char_name = ''
|
|
||||||
c.execute(
|
|
||||||
'''select name from character where character_id = :x''', {'x': i[1]})
|
|
||||||
y = c.fetchone()
|
|
||||||
if y is not None:
|
|
||||||
char_name = y[0]
|
|
||||||
char = {
|
|
||||||
"is_uncapped_override": int2b(i[14]),
|
|
||||||
"is_uncapped": int2b(i[13]),
|
|
||||||
"uncap_cores": [],
|
|
||||||
"char_type": i[12],
|
|
||||||
"skill_id_uncap": i[11],
|
|
||||||
"skill_requires_uncap": int2b(i[10]),
|
|
||||||
"skill_unlock_level": i[9],
|
|
||||||
"skill_id": i[8],
|
|
||||||
"overdrive": i[7],
|
|
||||||
"prog": i[6],
|
|
||||||
"frag": i[5],
|
|
||||||
"level_exp": i[4],
|
|
||||||
"exp": i[3],
|
|
||||||
"level": i[2],
|
|
||||||
"name": char_name,
|
|
||||||
"character_id": i[1]
|
|
||||||
}
|
|
||||||
if i[1] == 21:
|
|
||||||
char["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
|
|
||||||
s.append(char)
|
|
||||||
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
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:
|
|
||||||
s.append({
|
|
||||||
"is_mutual": is_mutual,
|
|
||||||
"is_char_uncapped_override": int2b(y[9]),
|
|
||||||
"is_char_uncapped": int2b(y[8]),
|
|
||||||
"is_skill_sealed": int2b(y[7]),
|
|
||||||
"rating": y[5],
|
|
||||||
"join_date": int(y[3]),
|
|
||||||
"character": y[6],
|
|
||||||
"recent_score": get_recent_score(c, i[0]),
|
|
||||||
"name": y[1],
|
|
||||||
"user_id": i[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_singles(c, user_id):
|
|
||||||
# 得到用户的单曲,返回列表
|
|
||||||
c.execute('''select * from user_item where user_id = :user_id and type = "single"''',
|
|
||||||
{'user_id': user_id})
|
|
||||||
x = c.fetchall()
|
|
||||||
if not x:
|
|
||||||
return []
|
|
||||||
|
|
||||||
re = []
|
|
||||||
for i in x:
|
|
||||||
re.append(i[1])
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_packs(c, user_id):
|
|
||||||
# 得到用户的曲包,返回列表
|
|
||||||
c.execute('''select * from user_item where user_id = :user_id and type = "pack"''',
|
|
||||||
{'user_id': user_id})
|
|
||||||
x = c.fetchall()
|
|
||||||
if not x:
|
|
||||||
return []
|
|
||||||
|
|
||||||
re = []
|
|
||||||
for i in x:
|
|
||||||
re.append(i[1])
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def get_value_0(c, user_id):
|
|
||||||
# 构造value id=0的数据,返回字典
|
|
||||||
c.execute('''select * from user where user_id = :x''', {'x': user_id})
|
|
||||||
x = c.fetchone()
|
|
||||||
r = {}
|
|
||||||
if x is not None:
|
|
||||||
user_character = get_user_character(c, user_id)
|
|
||||||
characters = []
|
|
||||||
for i in user_character:
|
|
||||||
characters.append(i['character_id'])
|
|
||||||
|
|
||||||
r = {"is_aprilfools": False,
|
|
||||||
"curr_available_maps": [],
|
|
||||||
"character_stats": user_character,
|
|
||||||
"friends": get_user_friend(c, user_id),
|
|
||||||
"settings": {
|
|
||||||
"favorite_character": x[23],
|
|
||||||
"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": x[6],
|
|
||||||
"is_locked_name_duplicate": False,
|
|
||||||
"is_skill_sealed": int2b(x[7]),
|
|
||||||
"current_map": x[25],
|
|
||||||
"prog_boost": 0,
|
|
||||||
"next_fragstam_ts": -1,
|
|
||||||
"max_stamina_ts": 1586274871917,
|
|
||||||
"stamina": 12,
|
|
||||||
"world_unlocks": ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5"],
|
|
||||||
"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"],
|
|
||||||
"singles": get_user_singles(c, user_id), # ["dataerror", "yourvoiceso", "crosssoul", "impurebird", "auxesia", "modelista", "yozakurafubuki", "surrender", "metallicpunisher", "carminescythe", "bethere", "callmyname", "fallensquare", "dropdead", "alexandrite", "astraltale", "phantasia", "empireofwinter", "libertas", "dottodot", "dreadnought", "mirzam", "heavenlycaress", "filament", "avantraze", "battlenoone", "saikyostronger", "izana", "einherjar", "laqryma", "amygdata", "altale", "feelssoright", "scarletcage", "teriqma", "mahoroba", "badtek", "maliciousmischance", "buchigireberserker", "galaxyfriends", "xeraphinite", "xanatos"]
|
|
||||||
"packs": get_user_packs(c, user_id),
|
|
||||||
# ["vs", "extend", "dynamix", "prelude", "core", "yugamu", "omatsuri", "zettai", "mirai", "shiawase", "chunithm", "nijuusei", "groovecoaster", "rei", "tonesphere", "lanota"]
|
|
||||||
"characters": characters,
|
|
||||||
"cores": [],
|
|
||||||
"recent_score": get_recent_score(c, user_id),
|
|
||||||
"max_friend": 50,
|
|
||||||
"rating": x[5],
|
|
||||||
"join_date": int(x[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def arc_aggregate_small(user_id):
|
|
||||||
# 返回用户数据
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
r = {"success": True,
|
|
||||||
"value": [{
|
|
||||||
"id": 0,
|
|
||||||
"value": get_value_0(c, user_id)
|
|
||||||
}]}
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def arc_aggregate_big(user_id):
|
|
||||||
# 返回用户数据和地图歌曲信息
|
|
||||||
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
r = {"success": True,
|
|
||||||
"value": [{
|
|
||||||
"id": 0,
|
|
||||||
"value": get_value_0(c, user_id)
|
|
||||||
}, {
|
|
||||||
"id": 1,
|
|
||||||
"value": server.arcpurchase.get_item(c, 'pack')
|
|
||||||
}, {
|
|
||||||
"id": 2,
|
|
||||||
"value": {}
|
|
||||||
}, {
|
|
||||||
"id": 3,
|
|
||||||
"value": {
|
|
||||||
"max_stamina": 12,
|
|
||||||
"stamina_recover_tick": 1800000,
|
|
||||||
"core_exp": 250,
|
|
||||||
"curr_ts": int(time.time())*1000,
|
|
||||||
"level_steps": [{
|
|
||||||
"level": 1,
|
|
||||||
"level_exp": 0
|
|
||||||
}, {
|
|
||||||
"level": 2,
|
|
||||||
"level_exp": 50
|
|
||||||
}, {
|
|
||||||
"level": 3,
|
|
||||||
"level_exp": 100
|
|
||||||
}, {
|
|
||||||
"level": 4,
|
|
||||||
"level_exp": 150
|
|
||||||
}, {
|
|
||||||
"level": 5,
|
|
||||||
"level_exp": 200
|
|
||||||
}, {
|
|
||||||
"level": 6,
|
|
||||||
"level_exp": 300
|
|
||||||
}, {
|
|
||||||
"level": 7,
|
|
||||||
"level_exp": 450
|
|
||||||
}, {
|
|
||||||
"level": 8,
|
|
||||||
"level_exp": 650
|
|
||||||
}, {
|
|
||||||
"level": 9,
|
|
||||||
"level_exp": 900
|
|
||||||
}, {
|
|
||||||
"level": 10,
|
|
||||||
"level_exp": 1200
|
|
||||||
}, {
|
|
||||||
"level": 11,
|
|
||||||
"level_exp": 1600
|
|
||||||
}, {
|
|
||||||
"level": 12,
|
|
||||||
"level_exp": 2100
|
|
||||||
}, {
|
|
||||||
"level": 13,
|
|
||||||
"level_exp": 2700
|
|
||||||
}, {
|
|
||||||
"level": 14,
|
|
||||||
"level_exp": 3400
|
|
||||||
}, {
|
|
||||||
"level": 15,
|
|
||||||
"level_exp": 4200
|
|
||||||
}, {
|
|
||||||
"level": 16,
|
|
||||||
"level_exp": 5100
|
|
||||||
}, {
|
|
||||||
"level": 17,
|
|
||||||
"level_exp": 6100
|
|
||||||
}, {
|
|
||||||
"level": 18,
|
|
||||||
"level_exp": 7200
|
|
||||||
}, {
|
|
||||||
"level": 19,
|
|
||||||
"level_exp": 8500
|
|
||||||
}, {
|
|
||||||
"level": 20,
|
|
||||||
"level_exp": 10000
|
|
||||||
}, {
|
|
||||||
"level": 21,
|
|
||||||
"level_exp": 11500
|
|
||||||
}, {
|
|
||||||
"level": 22,
|
|
||||||
"level_exp": 13000
|
|
||||||
}, {
|
|
||||||
"level": 23,
|
|
||||||
"level_exp": 14500
|
|
||||||
}, {
|
|
||||||
"level": 24,
|
|
||||||
"level_exp": 16000
|
|
||||||
}, {
|
|
||||||
"level": 25,
|
|
||||||
"level_exp": 17500
|
|
||||||
}, {
|
|
||||||
"level": 26,
|
|
||||||
"level_exp": 19000
|
|
||||||
}, {
|
|
||||||
"level": 27,
|
|
||||||
"level_exp": 20500
|
|
||||||
}, {
|
|
||||||
"level": 28,
|
|
||||||
"level_exp": 22000
|
|
||||||
}, {
|
|
||||||
"level": 29,
|
|
||||||
"level_exp": 23500
|
|
||||||
}, {
|
|
||||||
"level": 30,
|
|
||||||
"level_exp": 25000
|
|
||||||
}],
|
|
||||||
"world_ranking_enabled": False,
|
|
||||||
"is_byd_chapter_unlocked": True
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"id": 4,
|
|
||||||
"value": server.arcpurchase.get_user_present(c, user_id)
|
|
||||||
}, {
|
|
||||||
"id": 5,
|
|
||||||
"value": {
|
|
||||||
"current_map": server.arcworld.get_current_map(user_id),
|
|
||||||
"user_id": user_id,
|
|
||||||
"maps": server.arcworld.get_world_all(user_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return r
|
|
||||||
94
latest version/server/init.py
Normal file
94
latest version/server/init.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import os
|
||||||
|
from shutil import copy, copy2
|
||||||
|
from core.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`.')
|
||||||
|
|
||||||
|
return f
|
||||||
81
latest version/server/multiplayer.py
Normal file
81
latest version/server/multiplayer.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from multiprocessing import Pipe
|
||||||
|
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.linkplay import LocalMultiPlayer, Player, Room
|
||||||
|
from core.sql import Connect
|
||||||
|
from flask import Blueprint, request
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('multiplayer', __name__, url_prefix='/multiplayer')
|
||||||
|
|
||||||
|
conn1, conn2 = Pipe()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/room/create', methods=['POST']) # 创建房间
|
||||||
|
@auth_required(request)
|
||||||
|
def room_create(user_id):
|
||||||
|
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
||||||
|
return error_return(ArcError('The local udp server is down.', 151)), 404
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = LocalMultiPlayer(conn1)
|
||||||
|
user = Player(c, user_id)
|
||||||
|
user.get_song_unlock(request.json['clientSongMap'])
|
||||||
|
x.create_room(user)
|
||||||
|
r = x.to_dict()
|
||||||
|
r['endPoint'] = request.host.split(
|
||||||
|
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
||||||
|
r['port'] = int(Config.UDP_PORT)
|
||||||
|
return success_return(r)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e), 400
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/room/join/<room_code>', methods=['POST']) # 加入房间
|
||||||
|
@auth_required(request)
|
||||||
|
def room_join(user_id, room_code):
|
||||||
|
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
||||||
|
return error_return(ArcError('The local udp server is down.', 151)), 404
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = LocalMultiPlayer(conn1)
|
||||||
|
user = Player(c, user_id)
|
||||||
|
user.get_song_unlock(request.json['clientSongMap'])
|
||||||
|
room = Room()
|
||||||
|
room.room_code = room_code
|
||||||
|
x.join_room(room, user)
|
||||||
|
r = x.to_dict()
|
||||||
|
r['endPoint'] = request.host.split(
|
||||||
|
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
||||||
|
r['port'] = int(Config.UDP_PORT)
|
||||||
|
return success_return(r)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e), 400
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/update', methods=['POST']) # 更新房间
|
||||||
|
@auth_required(request)
|
||||||
|
def multiplayer_update(user_id):
|
||||||
|
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
||||||
|
return error_return(ArcError('The local udp server is down.', 151)), 404
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = LocalMultiPlayer(conn1)
|
||||||
|
user = Player(c, user_id)
|
||||||
|
user.token = int(request.json['token'])
|
||||||
|
x.update_room(user)
|
||||||
|
r = x.to_dict()
|
||||||
|
r['endPoint'] = request.host.split(
|
||||||
|
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
||||||
|
r['port'] = int(Config.UDP_PORT)
|
||||||
|
return success_return(r)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e), 400
|
||||||
|
return error_return()
|
||||||
107
latest version/server/others.py
Normal file
107
latest version/server/others.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import json
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
from core.download import DownloadList
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.system import GameInfo
|
||||||
|
from core.user import UserOnline
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
from .present import present_info
|
||||||
|
from .purchase import bundle_pack, bundle_bundle
|
||||||
|
from .score import song_score_friend
|
||||||
|
from .user import user_me
|
||||||
|
from .world import world_all
|
||||||
|
|
||||||
|
bp = Blueprint('others', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/game/info', methods=['GET']) # 系统信息
|
||||||
|
def game_info():
|
||||||
|
return success_return(GameInfo().to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/serve/download/me/song', methods=['GET']) # 歌曲下载
|
||||||
|
@auth_required(request)
|
||||||
|
def download_song(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = DownloadList(c, UserOnline(c, user_id))
|
||||||
|
x.song_ids = request.args.getlist('sid')
|
||||||
|
x.url_flag = json.loads(request.args.get('url', 'true'))
|
||||||
|
x.clear_user_download()
|
||||||
|
if x.is_limited and x.url_flag:
|
||||||
|
raise ArcError('You have reached the download limit.', 903)
|
||||||
|
|
||||||
|
x.add_songs()
|
||||||
|
return success_return(x.urls)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/finale/progress', methods=['GET'])
|
||||||
|
def finale_progress():
|
||||||
|
# 世界boss血条
|
||||||
|
return success_return({'percentage': 100000})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/finale/finale_start', methods=['POST'])
|
||||||
|
def finale_start():
|
||||||
|
# testify开始,对立再见
|
||||||
|
# 没数据
|
||||||
|
return success_return({})
|
||||||
|
|
||||||
|
|
||||||
|
map_dict = {'/user/me': user_me,
|
||||||
|
'/purchase/bundle/pack': bundle_pack,
|
||||||
|
'/serve/download/me/song': download_song,
|
||||||
|
'/game/info': game_info,
|
||||||
|
'/present/me': present_info,
|
||||||
|
'/world/map/me': world_all,
|
||||||
|
'/score/song/friend': song_score_friend,
|
||||||
|
'/purchase/bundle/bundle': bundle_bundle,
|
||||||
|
'/finale/progress': finale_progress}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/compose/aggregate', methods=['GET']) # 集成式请求
|
||||||
|
def aggregate():
|
||||||
|
try:
|
||||||
|
#global request
|
||||||
|
finally_response = {'success': True, 'value': []}
|
||||||
|
#request_ = request
|
||||||
|
get_list = json.loads(request.args.get('calls'))
|
||||||
|
if len(get_list) > 10:
|
||||||
|
# 请求太多驳回
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
for i in get_list:
|
||||||
|
endpoint = i['endpoint']
|
||||||
|
request.args = ImmutableMultiDict(
|
||||||
|
{key: value[0] for key, value in parse_qs(urlparse(endpoint).query).items()})
|
||||||
|
|
||||||
|
resp_t = map_dict[urlparse(endpoint).path]()
|
||||||
|
|
||||||
|
if hasattr(resp_t, "response"):
|
||||||
|
resp_t = resp_t.response[0].decode().rstrip('\n')
|
||||||
|
resp = json.loads(resp_t)
|
||||||
|
|
||||||
|
if hasattr(resp, 'get') and resp.get('success') is False:
|
||||||
|
finally_response = {'success': False, 'error_code': 7, 'extra': {
|
||||||
|
"id": i['id'], 'error_code': resp.get('error_code')}}
|
||||||
|
if "extra" in resp:
|
||||||
|
finally_response['extra']['extra'] = resp['extra']
|
||||||
|
#request = request_
|
||||||
|
return jsonify(finally_response)
|
||||||
|
|
||||||
|
finally_response['value'].append(
|
||||||
|
{'id': i.get('id'), 'value': resp['value'] if hasattr(resp, 'get') else resp})
|
||||||
|
|
||||||
|
#request = request_
|
||||||
|
return jsonify(finally_response)
|
||||||
|
except KeyError:
|
||||||
|
return error_return()
|
||||||
38
latest version/server/present.py
Normal file
38
latest version/server/present.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from core.error import ArcError
|
||||||
|
from core.present import UserPresent, UserPresentList
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserOnline
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('present', __name__, url_prefix='/present')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me', methods=['GET']) # 用户奖励信息
|
||||||
|
@auth_required(request)
|
||||||
|
def present_info(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserPresentList(c, UserOnline(c, user_id))
|
||||||
|
x.select_user_presents()
|
||||||
|
|
||||||
|
return success_return(x.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/claim/<present_id>', methods=['POST']) # 礼物确认
|
||||||
|
@auth_required(request)
|
||||||
|
def claim_present(user_id, present_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserPresent(c, UserOnline(c, user_id))
|
||||||
|
x.claim_user_present(present_id)
|
||||||
|
|
||||||
|
return success_return()
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
170
latest version/server/purchase.py
Normal file
170
latest version/server/purchase.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
|
from core.error import ArcError, ItemUnavailable
|
||||||
|
from core.item import ItemFactory, Stamina6
|
||||||
|
from core.purchase import Purchase, PurchaseList
|
||||||
|
from core.redeem import UserRedeem
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserOnline
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('purchase', __name__, url_prefix='/purchase')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/bundle/pack', methods=['GET']) # 曲包信息
|
||||||
|
@auth_required(request)
|
||||||
|
def bundle_pack(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = PurchaseList(c, UserOnline(c, user_id)
|
||||||
|
).select_from_type('pack')
|
||||||
|
return success_return(x.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/bundle/single', methods=['GET']) # 单曲购买信息获取
|
||||||
|
@auth_required(request)
|
||||||
|
def get_single(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = PurchaseList(c, UserOnline(c, user_id)
|
||||||
|
).select_from_type('single')
|
||||||
|
return success_return(x.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/bundle/bundle', methods=['GET']) # 捆绑包
|
||||||
|
def bundle_bundle():
|
||||||
|
return success_return([])
|
||||||
|
# 感觉上是定死的,没意义啊
|
||||||
|
# return success_return([{
|
||||||
|
# "name": "chronicles",
|
||||||
|
# "items": [{
|
||||||
|
# "type": "bundle",
|
||||||
|
# "id": "core"
|
||||||
|
# }, {
|
||||||
|
# "type": "bundle",
|
||||||
|
# "id": "prelude"
|
||||||
|
# }, {
|
||||||
|
# "type": "bundle",
|
||||||
|
# "id": "rei"
|
||||||
|
# }, {
|
||||||
|
# "type": "bundle",
|
||||||
|
# "id": "yugamu"
|
||||||
|
# }, {
|
||||||
|
# "type": "bundle",
|
||||||
|
# "id": "vs"
|
||||||
|
# }],
|
||||||
|
# "orig_price": 2000,
|
||||||
|
# "price": 2000,
|
||||||
|
# "discount_from": 1657152000000,
|
||||||
|
# "discount_to": 1758447999000,
|
||||||
|
# "discount_reason": "chronicle"
|
||||||
|
# }])
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/pack', methods=['POST']) # 曲包和单曲购买
|
||||||
|
@auth_required(request)
|
||||||
|
def buy_pack_or_single(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
if 'pack_id' in request.form:
|
||||||
|
purchase_name = request.form['pack_id']
|
||||||
|
elif 'single_id' in request.form:
|
||||||
|
purchase_name = request.form['single_id']
|
||||||
|
else:
|
||||||
|
return success_return()
|
||||||
|
|
||||||
|
x = Purchase(c, UserOnline(c, user_id)).select(purchase_name)
|
||||||
|
x.buy()
|
||||||
|
|
||||||
|
return success_return({
|
||||||
|
'user_id': x.user.user_id,
|
||||||
|
'ticket': x.user.ticket,
|
||||||
|
'packs': x.user.packs,
|
||||||
|
'singles': x.user.singles,
|
||||||
|
'characters': x.user.characters_list
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/item', methods=['POST']) # 特殊购买,world模式boost和stamina
|
||||||
|
@auth_required(request)
|
||||||
|
def buy_special(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
if 'item_id' not in request.form:
|
||||||
|
return error_return()
|
||||||
|
item_id = request.form['item_id']
|
||||||
|
|
||||||
|
x = Purchase(c, UserOnline(c, user_id))
|
||||||
|
x.purchase_name = item_id
|
||||||
|
x.price = 50
|
||||||
|
x.orig_price = 50
|
||||||
|
x.discount_from = -1
|
||||||
|
x.discount_to = -1
|
||||||
|
x.items = [ItemFactory(c).get_item(item_id)]
|
||||||
|
x.buy()
|
||||||
|
|
||||||
|
r = {'user_id': x.user.user_id, 'ticket': x.user.ticket}
|
||||||
|
if item_id == 'stamina6':
|
||||||
|
r['stamina'] = x.user.stamina.stamina
|
||||||
|
r['max_stamina_ts'] = x.user.stamina.max_stamina_ts
|
||||||
|
r['world_mode_locked_end_ts'] = -1
|
||||||
|
return success_return(r)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/stamina/<buy_stamina_type>', methods=['POST']) # 购买体力
|
||||||
|
@auth_required(request)
|
||||||
|
def purchase_stamina(user_id, buy_stamina_type):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
if buy_stamina_type != 'fragment':
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user.select_user_one_column('next_fragstam_ts', -1)
|
||||||
|
now = int(time()*1000)
|
||||||
|
if user.next_fragstam_ts > now:
|
||||||
|
return ItemUnavailable('Buying stamina by fragment is not available yet.', 905)
|
||||||
|
|
||||||
|
user.update_user_one_column(
|
||||||
|
'next_fragstam_ts', now + 24 * 3600 * 1000)
|
||||||
|
s = Stamina6(c)
|
||||||
|
s.user_claim_item(user)
|
||||||
|
return success_return({
|
||||||
|
"user_id": user.user_id,
|
||||||
|
"stamina": user.stamina.stamina,
|
||||||
|
"max_stamina_ts": user.stamina.max_stamina_ts,
|
||||||
|
"next_fragstam_ts": user.next_fragstam_ts,
|
||||||
|
'world_mode_locked_end_ts': -1
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/redeem', methods=['POST']) # 兑换码
|
||||||
|
@auth_required(request)
|
||||||
|
def redeem(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserRedeem(c, UserOnline(c, user_id))
|
||||||
|
x.claim_user_redeem(request.form['code'])
|
||||||
|
|
||||||
|
return success_return({"coupon": "fragment" + str(x.fragment) if x.fragment > 0 else ""})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
156
latest version/server/score.py
Normal file
156
latest version/server/score.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
from time import time
|
||||||
|
from core.course import CoursePlay
|
||||||
|
|
||||||
|
from core.error import ArcError, InputError
|
||||||
|
from core.rank import RankList
|
||||||
|
from core.score import UserPlay
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserOnline
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('score', __name__, url_prefix='/score')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/token', methods=['GET']) # 成绩上传所需的token,显然我不想验证
|
||||||
|
def score_token():
|
||||||
|
return success_return({'token': '1145141919810'})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/token/world', methods=['GET']) # 世界模式成绩上传所需的token
|
||||||
|
@auth_required(request)
|
||||||
|
def score_token_world(user_id):
|
||||||
|
|
||||||
|
stamina_multiply = int(
|
||||||
|
request.args['stamina_multiply']) if 'stamina_multiply' in request.args else 1
|
||||||
|
fragment_multiply = int(
|
||||||
|
request.args['fragment_multiply']) if 'fragment_multiply' in request.args else 100
|
||||||
|
prog_boost_multiply = int(
|
||||||
|
request.args['prog_boost_multiply']) if 'prog_boost_multiply' in request.args else 0
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserPlay(c, UserOnline(c, user_id))
|
||||||
|
x.song.set_chart(request.args['song_id'], int(
|
||||||
|
request.args['difficulty']))
|
||||||
|
x.set_play_state_for_world(stamina_multiply,
|
||||||
|
fragment_multiply, prog_boost_multiply)
|
||||||
|
return success_return({
|
||||||
|
"stamina": x.user.stamina.stamina,
|
||||||
|
"max_stamina_ts": x.user.stamina.max_stamina_ts,
|
||||||
|
"token": x.song_token
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/token/course', methods=['GET']) # 课题模式成绩上传所需的token
|
||||||
|
@auth_required(request)
|
||||||
|
def score_token_course(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
use_course_skip_purchase = request.args.get(
|
||||||
|
'use_course_skip_purchase', 'false') == 'true'
|
||||||
|
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user_play = UserPlay(c, user)
|
||||||
|
user_play.song_token = request.args.get('previous_token', None)
|
||||||
|
user_play.get_play_state()
|
||||||
|
|
||||||
|
status = 'created'
|
||||||
|
if user_play.course_play_state == -1:
|
||||||
|
# 没有token,课题模式刚开始
|
||||||
|
course_play = CoursePlay(c, user, user_play)
|
||||||
|
course_play.course_id = request.args['course_id']
|
||||||
|
user_play.course_play = course_play
|
||||||
|
user_play.set_play_state_for_course(
|
||||||
|
use_course_skip_purchase)
|
||||||
|
elif 0 <= user_play.course_play_state <= 3:
|
||||||
|
# 验证token
|
||||||
|
user_play.update_token_for_course()
|
||||||
|
else:
|
||||||
|
# 课题模式已经结束
|
||||||
|
user_play.clear_play_state()
|
||||||
|
user.select_user_about_stamina()
|
||||||
|
status = 'cleared' if user_play.course_play_state == 4 else 'failed'
|
||||||
|
|
||||||
|
return success_return({
|
||||||
|
"stamina": user.stamina.stamina,
|
||||||
|
"max_stamina_ts": user.stamina.max_stamina_ts,
|
||||||
|
"token": user_play.song_token,
|
||||||
|
'status': status
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/song', methods=['POST']) # 成绩上传
|
||||||
|
@auth_required(request)
|
||||||
|
def song_score_post(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
x = UserPlay(c, UserOnline(c, user_id))
|
||||||
|
x.song_token = request.form['song_token']
|
||||||
|
x.song_hash = request.form['song_hash']
|
||||||
|
x.song.set_chart(
|
||||||
|
request.form['song_id'], request.form['difficulty'])
|
||||||
|
x.set_score(request.form['score'], request.form['shiny_perfect_count'], request.form['perfect_count'], request.form['near_count'],
|
||||||
|
request.form['miss_count'], request.form['health'], request.form['modifier'], int(time() * 1000), request.form['clear_type'])
|
||||||
|
x.beyond_gauge = int(request.form['beyond_gauge'])
|
||||||
|
x.submission_hash = request.form['submission_hash']
|
||||||
|
if not x.is_valid:
|
||||||
|
raise InputError('Invalid score.', 107)
|
||||||
|
x.upload_score()
|
||||||
|
return success_return(x.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/song', methods=['GET']) # TOP20
|
||||||
|
@auth_required(request)
|
||||||
|
def song_score_top(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
rank_list = RankList(c)
|
||||||
|
rank_list.song.set_chart(request.args.get(
|
||||||
|
'song_id'), request.args.get('difficulty'))
|
||||||
|
rank_list.select_top()
|
||||||
|
return success_return(rank_list.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/song/me', methods=['GET']) # 我的排名,默认最多20
|
||||||
|
@auth_required(request)
|
||||||
|
def song_score_me(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
rank_list = RankList(c)
|
||||||
|
rank_list.song.set_chart(request.args.get(
|
||||||
|
'song_id'), request.args.get('difficulty'))
|
||||||
|
rank_list.select_me(UserOnline(c, user_id))
|
||||||
|
return success_return(rank_list.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/song/friend', methods=['GET']) # 好友排名,默认最多50
|
||||||
|
@auth_required(request)
|
||||||
|
def song_score_friend(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
rank_list = RankList(c)
|
||||||
|
rank_list.song.set_chart(request.args.get(
|
||||||
|
'song_id'), request.args.get('difficulty'))
|
||||||
|
rank_list.select_friend(UserOnline(c, user_id))
|
||||||
|
return success_return(rank_list.to_dict_list())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
import server.info
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
# 角色改变,包括技能封印的改变,返回成功与否的布尔值
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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 not None:
|
|
||||||
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': x[0], 'd': x[1], 'e': user_id})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def change_char_uncap(user_id, character_id):
|
|
||||||
# 角色觉醒改变,返回字典
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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()
|
|
||||||
r = None
|
|
||||||
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})
|
|
||||||
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 where user_id = :a and character_id = :b''',
|
|
||||||
{'a': user_id, 'b': character_id})
|
|
||||||
y = c.fetchone()
|
|
||||||
c.execute(
|
|
||||||
'''select name from character where character_id = :x''', {'x': y[1]})
|
|
||||||
z = c.fetchone()
|
|
||||||
if z is not None:
|
|
||||||
char_name = z[0]
|
|
||||||
if y is not None:
|
|
||||||
r = {
|
|
||||||
"is_uncapped_override": int2b(y[14]),
|
|
||||||
"is_uncapped": int2b(y[13]),
|
|
||||||
"uncap_cores": [],
|
|
||||||
"char_type": y[12],
|
|
||||||
"skill_id_uncap": y[11],
|
|
||||||
"skill_requires_uncap": int2b(y[10]),
|
|
||||||
"skill_unlock_level": y[9],
|
|
||||||
"skill_id": y[8],
|
|
||||||
"overdrive": y[7],
|
|
||||||
"prog": y[6],
|
|
||||||
"frag": y[5],
|
|
||||||
"level_exp": y[4],
|
|
||||||
"exp": y[3],
|
|
||||||
"level": y[2],
|
|
||||||
"name": char_name,
|
|
||||||
"character_id": y[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def arc_sys_set(user_id, value, set_arg):
|
|
||||||
# 三个设置,PTT隐藏、体力满通知、最爱角色,无返回
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
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})
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def arc_add_friend(user_id, friend_id):
|
|
||||||
# 加好友,返回好友列表,或者是错误码602、604
|
|
||||||
if user_id == friend_id:
|
|
||||||
return 604
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
|
||||||
{'x': user_id, 'y': friend_id})
|
|
||||||
r = None
|
|
||||||
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:
|
|
||||||
return 602
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def arc_delete_friend(user_id, friend_id):
|
|
||||||
# 删好友,返回好友列表
|
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
|
||||||
{'x': user_id, 'y': friend_id})
|
|
||||||
r = None
|
|
||||||
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)
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return r
|
|
||||||
193
latest version/server/user.py
Normal file
193
latest version/server/user.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
from core.character import UserCharacter
|
||||||
|
from core.error import ArcError, NoAccess
|
||||||
|
from core.item import ItemCore
|
||||||
|
from core.save import SaveData
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import User, UserLogin, UserOnline, UserRegister
|
||||||
|
from flask import Blueprint, request
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
from .auth import auth_required
|
||||||
|
from .func import error_return, success_return
|
||||||
|
|
||||||
|
bp = Blueprint('user', __name__, url_prefix='/user')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', 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(NoAccess('Wrong app version.', 1203))
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
new_user = UserRegister(c)
|
||||||
|
new_user.set_name(request.form['name'])
|
||||||
|
new_user.set_password(request.form['password'])
|
||||||
|
new_user.set_email(request.form['email'])
|
||||||
|
if 'device_id' in request.form:
|
||||||
|
device_id = request.form['device_id']
|
||||||
|
else:
|
||||||
|
device_id = 'low_version'
|
||||||
|
|
||||||
|
new_user.register()
|
||||||
|
|
||||||
|
# 注册后自动登录
|
||||||
|
user = UserLogin(c)
|
||||||
|
user.login(new_user.name, new_user.password,
|
||||||
|
device_id, request.remote_addr)
|
||||||
|
return success_return({'user_id': user.user_id, 'access_token': user.token})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me', methods=['GET']) # 用户信息
|
||||||
|
@auth_required(request)
|
||||||
|
def user_me(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
return success_return(UserOnline(c, user_id).to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/character', methods=['POST']) # 角色切换
|
||||||
|
@auth_required(request)
|
||||||
|
def character_change(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user.change_character(
|
||||||
|
int(request.form['character']), request.form['skill_sealed'] == 'true')
|
||||||
|
|
||||||
|
return success_return({'user_id': user.user_id, 'character': user.character.character_id})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
# 角色觉醒切换
|
||||||
|
@bp.route('/me/character/<int:character_id>/toggle_uncap', methods=['POST'])
|
||||||
|
@auth_required(request)
|
||||||
|
def toggle_uncap(user_id, character_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = User()
|
||||||
|
user.user_id = user_id
|
||||||
|
character = UserCharacter(c, character_id)
|
||||||
|
character.change_uncap_override(user)
|
||||||
|
character.select_character_info(user)
|
||||||
|
return success_return({'user_id': user.user_id, 'character': [character.to_dict()]})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
# 角色觉醒
|
||||||
|
@bp.route('/me/character/<int:character_id>/uncap', methods=['POST'])
|
||||||
|
@auth_required(request)
|
||||||
|
def character_first_uncap(user_id, character_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
character = UserCharacter(c, character_id)
|
||||||
|
character.select_character_info(user)
|
||||||
|
character.character_uncap(user)
|
||||||
|
return success_return({'user_id': user.user_id, 'character': [character.to_dict()], 'cores': user.cores})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
# 角色使用以太之滴
|
||||||
|
@bp.route('/me/character/<int:character_id>/exp', methods=['POST'])
|
||||||
|
@auth_required(request)
|
||||||
|
def character_exp(user_id, character_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
character = UserCharacter(c, character_id)
|
||||||
|
character.select_character_info(user)
|
||||||
|
core = ItemCore(c)
|
||||||
|
core.amount = - int(request.form['amount'])
|
||||||
|
core.item_id = 'core_generic'
|
||||||
|
character.upgrade_by_core(user, core)
|
||||||
|
return success_return({'user_id': user.user_id, 'character': [character.to_dict], 'cores': user.cores})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/save', methods=['GET']) # 从云端同步
|
||||||
|
@auth_required(request)
|
||||||
|
def cloud_get(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = User()
|
||||||
|
user.user_id = user_id
|
||||||
|
save = SaveData(c)
|
||||||
|
save.select_all(user)
|
||||||
|
return success_return(save.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/save', methods=['POST']) # 向云端同步
|
||||||
|
@auth_required(request)
|
||||||
|
def cloud_post(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = User()
|
||||||
|
user.user_id = user_id
|
||||||
|
save = SaveData(c)
|
||||||
|
save.set_value(
|
||||||
|
'scores_data', request.form['scores_data'], request.form['scores_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'clearlamps_data', request.form['clearlamps_data'], request.form['clearlamps_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'clearedsongs_data', request.form['clearedsongs_data'], request.form['clearedsongs_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'unlocklist_data', request.form['unlocklist_data'], request.form['unlocklist_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'installid_data', request.form['installid_data'], request.form['installid_checksum'])
|
||||||
|
save.set_value('devicemodelname_data',
|
||||||
|
request.form['devicemodelname_data'], request.form['devicemodelname_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'story_data', request.form['story_data'], request.form['story_checksum'])
|
||||||
|
save.set_value(
|
||||||
|
'finalestate_data', request.form['finalestate_data'], request.form['finalestate_checksum'])
|
||||||
|
|
||||||
|
save.update_all(user)
|
||||||
|
return success_return({'user_id': user.user_id})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/setting/<set_arg>', methods=['POST']) # 三个设置
|
||||||
|
@auth_required(request)
|
||||||
|
def sys_set(user_id, set_arg):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
value = request.form['value']
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
if 'favorite_character' == set_arg:
|
||||||
|
user.change_favorite_character(int(value))
|
||||||
|
else:
|
||||||
|
value = 'true' == value
|
||||||
|
if 'is_hide_rating' == set_arg or 'max_stamina_notification_enabled' == set_arg:
|
||||||
|
user.update_user_one_column(set_arg, value)
|
||||||
|
return success_return(user.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/me/request_delete', methods=['POST']) # 删除账号
|
||||||
|
@auth_required(request)
|
||||||
|
def user_delete(user_id):
|
||||||
|
return error_return(ArcError('Cannot delete the account.', 151)), 404
|
||||||
56
latest version/server/world.py
Normal file
56
latest version/server/world.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
from core.error import ArcError
|
||||||
|
from core.sql import Connect
|
||||||
|
from core.user import UserOnline
|
||||||
|
from core.world import UserMap, get_world_all
|
||||||
|
from .func import error_return, success_return
|
||||||
|
from .auth import auth_required
|
||||||
|
|
||||||
|
bp = Blueprint('world', __name__, url_prefix='/world')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/map/me', methods=['GET']) # 获得世界模式信息,所有地图
|
||||||
|
@auth_required(request)
|
||||||
|
def world_all(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
user = UserOnline(c, user_id)
|
||||||
|
user.select_user_about_current_map()
|
||||||
|
return success_return({
|
||||||
|
"current_map": user.current_map.map_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
"maps": [x.to_dict(has_map_info=True, has_rewards=True) for x in get_world_all(c, user)]
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/map/me', methods=['POST']) # 进入地图
|
||||||
|
@auth_required(request)
|
||||||
|
def world_in(user_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
arcmap = UserMap(c, request.form['map_id'], UserOnline(c, user_id))
|
||||||
|
if arcmap.unlock():
|
||||||
|
return success_return(arcmap.to_dict())
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/map/me/<map_id>', methods=['GET']) # 获得单个地图完整信息
|
||||||
|
@auth_required(request)
|
||||||
|
def world_one(user_id, map_id):
|
||||||
|
with Connect() as c:
|
||||||
|
try:
|
||||||
|
arcmap = UserMap(c, map_id, UserOnline(c, user_id))
|
||||||
|
arcmap.change_user_current_map()
|
||||||
|
return success_return({
|
||||||
|
"user_id": user_id,
|
||||||
|
"current_map": map_id,
|
||||||
|
"maps": [arcmap.to_dict(has_map_info=True, has_steps=True)]
|
||||||
|
})
|
||||||
|
except ArcError as e:
|
||||||
|
return error_return(e)
|
||||||
|
return error_return()
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[CONFIG]
|
|
||||||
HOST = 192.168.1.113
|
|
||||||
PORT = 80
|
|
||||||
|
|
||||||
[WEB]
|
|
||||||
USERNAME = admin
|
|
||||||
PASSWORD = admin
|
|
||||||
256
latest version/setting.py
Normal file
256
latest version/setting.py
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
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 = '/divide/20'
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
允许使用的游戏版本,若为空,则默认全部允许
|
||||||
|
Allowed game versions
|
||||||
|
If it is blank, all are allowed.
|
||||||
|
'''
|
||||||
|
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '4.0.0', '4.0.0c']
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
联机功能的端口号,若为空,则默认不开启联机功能
|
||||||
|
Port of your link play server
|
||||||
|
If it is blank, link play will be unavailable.
|
||||||
|
'''
|
||||||
|
UDP_PORT = '10900'
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
联机功能地址,留空则自动获取
|
||||||
|
Link Play address
|
||||||
|
If left blank, it will be obtained automatically.
|
||||||
|
'''
|
||||||
|
LINK_PLAY_HOST = '' # ***.com
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
SSL证书路径
|
||||||
|
留空则使用HTTP
|
||||||
|
SSL certificate path
|
||||||
|
If left blank, use HTTP.
|
||||||
|
'''
|
||||||
|
SSL_CERT = '' # *.pem
|
||||||
|
SSL_KEY = '' # *.key
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
愚人节模式开关
|
||||||
|
Switch of April Fool's Day
|
||||||
|
'''
|
||||||
|
IS_APRILFOOLS = False
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
世界排名的最大显示数量
|
||||||
|
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 sceneries is enabled
|
||||||
|
'''
|
||||||
|
WORLD_SCENERY_FULL_UNLOCK = True
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
是否强制使用全解锁云端存档
|
||||||
|
If forcing full unlocked cloud save is enabled
|
||||||
|
请注意,当前对于最终结局的判定为固定在`Testify`解锁之前
|
||||||
|
Please note that the current setting of the finale state is before the unlock of `Testify`
|
||||||
|
'''
|
||||||
|
SAVE_FULL_UNLOCK = False
|
||||||
|
'''
|
||||||
|
--------------------
|
||||||
|
'''
|
||||||
@@ -20,4 +20,4 @@
|
|||||||
{% endfor %} {% block content %}{% endblock %}
|
{% endfor %} {% block content %}{% endblock %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer id="footer" class="footer">Made by Lost@2020</footer>
|
<footer id="footer" class="footer">Made by Lost@2020-2022</footer>
|
||||||
@@ -16,26 +16,54 @@
|
|||||||
<span class="char-name">{{char['name']}}</span>
|
<span class="char-name">{{char['name']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<span>Level: </span>
|
<span>Max level: </span>
|
||||||
<span class="char-num">{{char['level']}}</span>
|
<span class="char-num">{{char['max_level']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<span>Frag: </span>
|
<span>Frag-level 1: </span>
|
||||||
<span class="char-num">{{char['frag']}}</span>
|
<span class="char-num">{{char['frag1']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<span>Prog: </span>
|
<span>Prog-level 1: </span>
|
||||||
<span class="char-num">{{char['prog']}}</span>
|
<span class="char-num">{{char['prog1']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<span>Overdrive: </span>
|
<span>Overdrive-level 1: </span>
|
||||||
<span class="char-num">{{char['overdrive']}}</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 />
|
<br />
|
||||||
|
|
||||||
<span>Skill id: </span>
|
<span>Skill id: </span>
|
||||||
<span class="char-num">{{char['skill_id']}}</span>
|
<span class="char-num">{{char['skill_id']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<span>Skill unlock level: </span>
|
||||||
|
<span class="char-num">{{char['skill_unlock_level']}}</span>
|
||||||
|
<br />
|
||||||
|
|
||||||
<span>Uncapped skill id: </span>
|
<span>Uncapped skill id: </span>
|
||||||
<span class="char-num">{{char['skill_id_uncap']}}</span>
|
<span class="char-num">{{char['skill_id_uncap']}}</span>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -20,24 +20,6 @@
|
|||||||
<span class="char-num">{{item['is_available']}}</span>
|
<span class="char-num">{{item['is_available']}}</span>
|
||||||
<br />
|
<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 />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% if not loop.last %}
|
{% if not loop.last %}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
<div class="name">{{user['name']}}
|
<div class="name">{{user['name']}}
|
||||||
<span class="rank">UID: {{user['user_id']}}</span>
|
<span class="rank">UID: {{user['user_id']}}</span>
|
||||||
<span class="rank">User code: {{user['user_code']}}</span>
|
<span class="rank">User code: {{user['user_code']}}</span>
|
||||||
|
{% if user['ban_flag'] %}
|
||||||
|
<span class="rank">Banned</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="join-date">注册于 Registered in: {{user['join_date']}}</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">PTT: {{'%0.2f'|format(user['rating_ptt']/100|float)}}</div>
|
||||||
|
|||||||
55
latest version/templates/web/allpresent.html
Normal file
55
latest version/templates/web/allpresent.html
Normal 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 %}
|
||||||
65
latest version/templates/web/allpurchase.html
Normal file
65
latest version/templates/web/allpurchase.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{% 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 />
|
||||||
|
|
||||||
|
<span>Whether it is allowed to purchase with exchange certificate: </span>
|
||||||
|
<span class="char-num">{{item['discount_reason']}}</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 %}
|
||||||
58
latest version/templates/web/allredeem.html
Normal file
58
latest version/templates/web/allredeem.html
Normal 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 %}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<span>Sid: </span>
|
<span>Sid: </span>
|
||||||
<span class="song-title">{{song['song_id']}}</span>
|
<span class="song-title">{{song['song_id']}}</span>
|
||||||
<br />
|
<br />
|
||||||
<span>Name_en: </span>
|
<span>Name: </span>
|
||||||
<span class="song-title">{{song['name_en']}}</span>
|
<span class="song-title">{{song['name_en']}}</span>
|
||||||
<br />
|
<br />
|
||||||
<div>铺面定数 Chart const: </div>
|
<div>铺面定数 Chart const: </div>
|
||||||
|
|||||||
37
latest version/templates/web/banuser.html
Normal file
37
latest version/templates/web/banuser.html
Normal 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 %}
|
||||||
@@ -12,15 +12,6 @@
|
|||||||
<label for="level">Level</label>
|
<label for="level">Level</label>
|
||||||
<input name="level" id="level">
|
<input name="level" id="level">
|
||||||
|
|
||||||
<label for="frag">Frag</label>
|
|
||||||
<input name="frag" id="frag">
|
|
||||||
|
|
||||||
<label for="prog">Prog</label>
|
|
||||||
<input name="prog" id="prog">
|
|
||||||
|
|
||||||
<label for="overdrive">Overdrive</label>
|
|
||||||
<input name="overdrive" id="overdrive">
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<select name="skill_id">
|
<select name="skill_id">
|
||||||
<option value="">Skill id</option>
|
<option value="">Skill id</option>
|
||||||
|
|||||||
@@ -5,37 +5,43 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="/web/changeitem" method="post">
|
<form action="/web/changeitem" method="post">
|
||||||
<div class="title">Change the item</div>
|
<div class="title">Add the item</div>
|
||||||
<label for="item_id">ID of the item</label>
|
<label for="item_id">ID of the item</label>
|
||||||
<input name="item_id" id="item_id" required>
|
<input name="item_id" id="item_id" required>
|
||||||
<label for="type">Type</label>
|
<label for="type">Type</label>
|
||||||
<div>
|
<div>
|
||||||
<select name="type">
|
<select name="type">
|
||||||
<option value='single'>Single</option>
|
<option value='single' selected>Single</option>
|
||||||
<option value='pack'>Pack</option>
|
<option value='pack'>Pack</option>
|
||||||
|
<option value='world_song'>World song</option>
|
||||||
|
<option value='character'>Character</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="content">可修改项 Modifiable items:</div>
|
|
||||||
<div><label for="is_available">是否可用 Is available:</label>
|
<div><label for="is_available">是否可用 Is available:</label>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<label><input type="radio" name="is_available" value=1>True</label>
|
<label><input type="radio" name="is_available" checked value=1>True</label>
|
||||||
<br />
|
<br />
|
||||||
<label><input type="radio" name="is_available" value=0>False</label>
|
<label><input type="radio" name="is_available" value=0>False</label>
|
||||||
</div>
|
</div>
|
||||||
<label for="price">Price</label>
|
<br />
|
||||||
<input name="price" id="price">
|
<input type="submit" value="Add">
|
||||||
<label for="orig_price">Original price</label>
|
</form>
|
||||||
<input name="orig_price" id="orig_price">
|
<form action="/web/changeitem/delete" method="post">
|
||||||
<label for="discount_from">Discount from</label>
|
<div class="title">Delete the item</div>
|
||||||
<input type="datetime-local" name="discount_from" id="discount_from">
|
<label for="item_id">ID of the item</label>
|
||||||
<label for="discount_to">Discount to</label>
|
<input name="item_id" id="item_id" required>
|
||||||
<input type="datetime-local" name="discount_to" id="discount_to">
|
<label for="type">Type</label>
|
||||||
<div class="content">如果某些不需要修改,留空即可</div>
|
<div>
|
||||||
<div class="content">If some things do not need to be modified, leave it blank.</div>
|
<select name="type">
|
||||||
<div class="content">时间填写是一个HTML5控件</div>
|
<option value='single' selected>Single</option>
|
||||||
<div class="content">Time filling is an HTML5 control.</div>
|
<option value='pack'>Pack</option>
|
||||||
<input type="submit" value="Edit">
|
<option value='world_song'>World song</option>
|
||||||
|
<option value='character'>Character</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="Delete">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
50
latest version/templates/web/changepresent.html
Normal file
50
latest version/templates/web/changepresent.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% 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>
|
||||||
|
<option value='anni5tix'>Anniversary 5 ticket</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 %}
|
||||||
36
latest version/templates/web/changepurchase.html
Normal file
36
latest version/templates/web/changepurchase.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{% 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>是否允许使用兑换券购买 Whether it is allowed to purchase with Anniversary 5 ticket:
|
||||||
|
<label><input type="radio" name="discount_reason" value="0">No</label>
|
||||||
|
<label><input type="radio" name="discount_reason" value="anni5tix">Yes</label>
|
||||||
|
</div>
|
||||||
|
<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 %}
|
||||||
48
latest version/templates/web/changepurchaseitem.html
Normal file
48
latest version/templates/web/changepurchaseitem.html
Normal 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 %}
|
||||||
64
latest version/templates/web/changeredeem.html
Normal file
64
latest version/templates/web/changeredeem.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{% 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>
|
||||||
|
<option value='anni5tix'>Anniversary 5 ticket</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 %}
|
||||||
38
latest version/templates/web/changescore.html
Normal file
38
latest version/templates/web/changescore.html
Normal 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 %}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<input name="rating_ftr" id="rating_ftr" required>
|
<input name="rating_ftr" id="rating_ftr" required>
|
||||||
<label for="rating_byd">Beyond chart const</label>
|
<label for="rating_byd">Beyond chart const</label>
|
||||||
<input name="rating_byd" id="rating_byd" required>
|
<input name="rating_byd" id="rating_byd" required>
|
||||||
<div class="content">如果没有某个铺面,应该填入-1。</div>
|
<div class="content">如果没有某个谱面,应该填入-1。</div>
|
||||||
<div class="content">If there is no some chart, fill in -1 please.</div>
|
<div class="content">If there is no some chart, fill in -1 please.</div>
|
||||||
<input type="submit" value="Add">
|
<input type="submit" value="Add">
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
24
latest version/templates/web/changeuserpwd.html
Normal file
24
latest version/templates/web/changeuserpwd.html
Normal 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 %}
|
||||||
32
latest version/templates/web/deliverpresent.html
Normal file
32
latest version/templates/web/deliverpresent.html
Normal 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 %}
|
||||||
@@ -13,19 +13,31 @@
|
|||||||
<a href="{{ url_for('index.single_player_score') }}">单个玩家成绩查询 Single player score</a></br></br>
|
<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.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_player') }}">所有玩家信息查询 All players</a></br></br>
|
||||||
<a href="{{ url_for('index.all_song') }}">铺面信息查询 All songs</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_character') }}">角色信息查询 All characters</a></br></br>
|
||||||
<a href="{{ url_for('index.all_item') }}">购买信息查询 All items</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>
|
<a href="{{ url_for('index.single_chart_top') }}">单个铺面排行榜查询 Single song chart tops</a>
|
||||||
<hr>
|
<hr>
|
||||||
<h1>系统方面 System</h1>
|
<h1>系统方面 System</h1>
|
||||||
<a href="{{ url_for('index.update_database') }}">数据库更新 Update databases</a></br></br>
|
<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.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_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_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_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') }}">用户信息修改 Change user information</a></br></br>
|
||||||
<a href="{{ url_for('index.change_user_purchase') }}">用户购买信息修改 Change user purchase information</a>
|
<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 %}
|
{% endblock %}
|
||||||
24
latest version/templates/web/redeem.html
Normal file
24
latest version/templates/web/redeem.html
Normal 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 %}
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
<div class="name">{{user['name']}}
|
<div class="name">{{user['name']}}
|
||||||
<span class="rank">UID: {{user['user_id']}}</span>
|
<span class="rank">UID: {{user['user_id']}}</span>
|
||||||
<span class="rank">User code: {{user['user_code']}}</span>
|
<span class="rank">User code: {{user['user_code']}}</span>
|
||||||
|
{% if user['ban_flag'] %}
|
||||||
|
<span class="rank">Banned</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
|
<div class="join-date">注册于 Registered in: {{user['join_date']}}</div>
|
||||||
<div class="ptt">Memories: {{user['ticket']}}</div>
|
<div class="ptt">Memories: {{user['ticket']}}</div>
|
||||||
|
|||||||
@@ -10,12 +10,34 @@
|
|||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的角色列表。<br />
|
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的全角色列表。<br />
|
||||||
可上传文件: arcaea_database.db和arcsong.db<br />
|
可上传文件: arcaea_database.db<br />
|
||||||
新数据库不存在的数据会被添加,存在的数据将不会被改变。<br /><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
|
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 />
|
characters owned by players.<br />
|
||||||
Uploadable files: arcaea_database.db & arcsong.db<br />
|
Uploadable files: arcaea_database.db<br />
|
||||||
Data that does not exist in the new database will be added and the existing data will not be changed.
|
Data that does not exist in the new database will be added and the existing duplicate data will also be changed.
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
0
latest version/udpserver/__init__.py
Normal file
0
latest version/udpserver/__init__.py
Normal file
24
latest version/udpserver/aes.py
Normal file
24
latest version/udpserver/aes.py
Normal 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()
|
||||||
180
latest version/udpserver/udp_class.py
Normal file
180
latest version/udpserver/udp_class.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
from .udp_config import Config
|
||||||
|
|
||||||
|
|
||||||
|
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' * Config.LINK_PLAY_UNLOCK_LENGTH
|
||||||
|
|
||||||
|
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' * Config.LINK_PLAY_UNLOCK_LENGTH
|
||||||
|
|
||||||
|
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' * Config.LINK_PLAY_UNLOCK_LENGTH)
|
||||||
|
for i in self.players:
|
||||||
|
if i.player_id != 0:
|
||||||
|
r &= bi(i.song_unlock)
|
||||||
|
|
||||||
|
self.song_unlock = b(r, Config.LINK_PLAY_UNLOCK_LENGTH)
|
||||||
|
|
||||||
|
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):
|
||||||
|
# 是否全部进入结算
|
||||||
|
if self.state == 8:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
||||||
11
latest version/udpserver/udp_config.py
Normal file
11
latest version/udpserver/udp_config.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class Config:
|
||||||
|
TIME_LIMIT = 3600000
|
||||||
|
|
||||||
|
COMMAND_INTERVAL = 1000000
|
||||||
|
|
||||||
|
COUNTDOWM_TIME = 3999
|
||||||
|
|
||||||
|
PLAYER_PRE_TIMEOUT = 3000000
|
||||||
|
PLAYER_TIMEOUT = 20000000
|
||||||
|
|
||||||
|
LINK_PLAY_UNLOCK_LENGTH = 512
|
||||||
223
latest version/udpserver/udp_main.py
Normal file
223
latest version/udpserver/udp_main.py
Normal 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
|
||||||
334
latest version/udpserver/udp_parser.py
Normal file
334
latest version/udpserver/udp_parser.py
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from .udp_class import Room, bi
|
||||||
|
from .udp_config import Config
|
||||||
|
from .udp_sender import CommandSender
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
flag_13 = False
|
||||||
|
# 离线判断
|
||||||
|
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 >= Config.PLAYER_PRE_TIMEOUT:
|
||||||
|
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_13 = True
|
||||||
|
|
||||||
|
flag_11 = False
|
||||||
|
flag_12 = 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
|
||||||
46
latest version/udpserver/udp_sender.py
Normal file
46
latest version/udpserver/udp_sender.py
Normal 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'
|
||||||
0
latest version/web/__init__.py
Normal file
0
latest version/web/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,8 @@
|
|||||||
from flask import (Blueprint, flash, g, redirect,
|
from flask import (Blueprint, flash, g, redirect,
|
||||||
render_template, request, session, url_for)
|
render_template, request, session, url_for)
|
||||||
import functools
|
import functools
|
||||||
import configparser
|
from setting import Config
|
||||||
|
import hashlib
|
||||||
|
|
||||||
bp = Blueprint('login', __name__, url_prefix='/web')
|
bp = Blueprint('login', __name__, url_prefix='/web')
|
||||||
|
|
||||||
@@ -15,18 +16,16 @@ def login():
|
|||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
if username != Config.USERNAME or password != Config.PASSWORD:
|
||||||
path = r'setting.ini'
|
|
||||||
config.read(path, encoding="utf-8")
|
|
||||||
USERNAME = config.get('WEB', 'USERNAME')
|
|
||||||
PASSWORD = config.get('WEB', 'PASSWORD')
|
|
||||||
|
|
||||||
if username != USERNAME and password != PASSWORD:
|
|
||||||
error = '错误的用户名或密码 Incorrect username or password.'
|
error = '错误的用户名或密码 Incorrect username or password.'
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
session.clear()
|
session.clear()
|
||||||
session['user_id'] = USERNAME + PASSWORD
|
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'))
|
return redirect(url_for('index.index'))
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
@@ -48,16 +47,14 @@ def login_required(view):
|
|||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
x = session.get('user_id')
|
x = session.get('user_id')
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
hash_session = Config.USERNAME + \
|
||||||
path = r'setting.ini'
|
hashlib.sha256(Config.PASSWORD.encode("utf8")).hexdigest()
|
||||||
config.read(path, encoding="utf-8")
|
hash_session = hashlib.sha256(hash_session.encode("utf8")).hexdigest()
|
||||||
USERNAME = config.get('WEB', 'USERNAME')
|
|
||||||
PASSWORD = config.get('WEB', 'PASSWORD')
|
|
||||||
|
|
||||||
if x != USERNAME + PASSWORD:
|
if x != hash_session:
|
||||||
return redirect(url_for('login.login'))
|
return redirect(url_for('login.login'))
|
||||||
|
|
||||||
g.user = {'user_id': x, 'username': USERNAME}
|
g.user = {'user_id': x, 'username': Config.USERNAME}
|
||||||
return view(**kwargs)
|
return view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import sqlite3
|
from core.sql import Connect
|
||||||
import time
|
import time
|
||||||
import json
|
import hashlib
|
||||||
import server.arcscore
|
from random import Random
|
||||||
|
from setting import Config
|
||||||
|
|
||||||
|
|
||||||
def int2b(x):
|
def int2b(x):
|
||||||
@@ -13,6 +14,17 @@ def int2b(x):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def random_str(randomlength=10):
|
||||||
|
# 随机生成字符串
|
||||||
|
s = ''
|
||||||
|
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
|
||||||
|
length = len(chars) - 1
|
||||||
|
random = Random()
|
||||||
|
for _ in range(randomlength):
|
||||||
|
s += chars[random.randint(0, length)]
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def get_table_info(c, table_name):
|
def get_table_info(c, table_name):
|
||||||
# 得到表结构,返回主键列表和字段名列表
|
# 得到表结构,返回主键列表和字段名列表
|
||||||
pk = []
|
pk = []
|
||||||
@@ -85,8 +97,25 @@ def get_sql_insert_table(table_name, field, value):
|
|||||||
return sql, sql_dict
|
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):
|
def update_one_table(c1, c2, table_name):
|
||||||
# 从c1向c2更新数据表,c2中存在的信息不变
|
# 从c1向c2更新数据表,c1中存在的信息不变
|
||||||
c1.execute(
|
c1.execute(
|
||||||
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
|
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
|
||||||
c2.execute(
|
c2.execute(
|
||||||
@@ -117,7 +146,11 @@ def update_one_table(c1, c2, table_name):
|
|||||||
sql = 'select exists(' + sql + ')'
|
sql = 'select exists(' + sql + ')'
|
||||||
c2.execute(sql, sql_dict)
|
c2.execute(sql, sql_dict)
|
||||||
|
|
||||||
if c2.fetchone() == (0,):
|
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(
|
sql, sql_dict = get_sql_insert_table(
|
||||||
table_name, field, list(y[i]))
|
table_name, field, list(y[i]))
|
||||||
c2.execute(sql, sql_dict)
|
c2.execute(sql, sql_dict)
|
||||||
@@ -127,76 +160,80 @@ def update_one_table(c1, c2, table_name):
|
|||||||
|
|
||||||
def update_user_char(c):
|
def update_user_char(c):
|
||||||
# 用character数据更新user_char
|
# 用character数据更新user_char
|
||||||
c.execute('''select * from character''')
|
c.execute('''select character_id, max_level, is_uncapped from character''')
|
||||||
x = c.fetchall()
|
x = c.fetchall()
|
||||||
c.execute('''select user_id from user''')
|
c.execute('''select user_id from user''')
|
||||||
y = c.fetchall()
|
y = c.fetchall()
|
||||||
if x and y:
|
if x and y:
|
||||||
for j in y:
|
for j in y:
|
||||||
for i in x:
|
for i in x:
|
||||||
c.execute('''delete from user_char where user_id=:a and character_id=:b''', {
|
c.execute('''delete from user_char_full where user_id=:a and character_id=:b''', {
|
||||||
'a': j[0], 'b': i[0]})
|
'a': j[0], 'b': i[0]})
|
||||||
c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', {
|
exp = 25000 if i[1] == 30 else 10000
|
||||||
'a': j[0], 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]})
|
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
|
||||||
|
(j[0], i[0], i[1], exp, i[2], 0))
|
||||||
|
|
||||||
|
|
||||||
def update_database():
|
def update_database():
|
||||||
# 将old数据库不存在数据加入到新数据库上,并删除old数据库
|
# 将old数据库不存在数据加入到新数据库上,并删除old数据库
|
||||||
# 对于arcaea_datebase.db,更新best_score,friend,recent30,user,user_world, user_item, user_save, login, present, user_present并用character数据更新user_char
|
# 对于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"):
|
if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"):
|
||||||
conn1 = sqlite3.connect('./database/old_arcaea_database.db')
|
with Connect('./database/old_arcaea_database.db') as c1:
|
||||||
c1 = conn1.cursor()
|
with Connect() as c2:
|
||||||
conn2 = sqlite3.connect('./database/arcaea_database.db')
|
|
||||||
c2 = conn2.cursor()
|
|
||||||
|
|
||||||
update_one_table(c1, c2, 'user')
|
update_one_table(c1, c2, 'user')
|
||||||
update_one_table(c1, c2, 'friend')
|
update_one_table(c1, c2, 'friend')
|
||||||
update_one_table(c1, c2, 'best_score')
|
update_one_table(c1, c2, 'best_score')
|
||||||
update_one_table(c1, c2, 'recent30')
|
update_one_table(c1, c2, 'recent30')
|
||||||
update_one_table(c1, c2, 'user_world')
|
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, '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, 'user_save')
|
||||||
update_one_table(c1, c2, 'login')
|
update_one_table(c1, c2, 'login')
|
||||||
update_one_table(c1, c2, 'present')
|
update_one_table(c1, c2, 'present')
|
||||||
update_one_table(c1, c2, 'user_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, 'chart')
|
||||||
|
update_one_table(c1, c2, 'user_course')
|
||||||
|
update_one_table(c1, c2, 'course')
|
||||||
|
update_one_table(c1, c2, 'course_item')
|
||||||
|
update_one_table(c1, c2, 'course_chart')
|
||||||
|
update_one_table(c1, c2, 'course_requirement')
|
||||||
|
|
||||||
update_user_char(c2)
|
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
|
||||||
|
|
||||||
conn1.commit()
|
|
||||||
conn1.close()
|
|
||||||
conn2.commit()
|
|
||||||
conn2.close()
|
|
||||||
os.remove('database/old_arcaea_database.db')
|
os.remove('database/old_arcaea_database.db')
|
||||||
|
|
||||||
# songs
|
|
||||||
if os.path.isfile("database/old_arcsong.db") and os.path.isfile("database/arcsong.db"):
|
|
||||||
conn1 = sqlite3.connect('./database/old_arcsong.db')
|
|
||||||
c1 = conn1.cursor()
|
|
||||||
conn2 = sqlite3.connect('./database/arcsong.db')
|
|
||||||
c2 = conn2.cursor()
|
|
||||||
|
|
||||||
update_one_table(c1, c2, 'songs')
|
|
||||||
|
|
||||||
conn1.commit()
|
|
||||||
conn1.close()
|
|
||||||
conn2.commit()
|
|
||||||
conn2.close()
|
|
||||||
os.remove('database/old_arcsong.db')
|
|
||||||
|
|
||||||
|
|
||||||
def unlock_all_user_item(c):
|
def unlock_all_user_item(c):
|
||||||
# 解锁所有用户购买
|
# 解锁所有用户购买
|
||||||
|
|
||||||
c.execute('''select user_id from user''')
|
c.execute('''select user_id from user''')
|
||||||
x = c.fetchall()
|
x = c.fetchall()
|
||||||
c.execute('''select item_id, type from item''')
|
c.execute('''select item_id, type from purchase_item''')
|
||||||
y = c.fetchall()
|
y = c.fetchall()
|
||||||
c.execute('''delete from user_item''')
|
|
||||||
if x and y:
|
if x and y:
|
||||||
for i in x:
|
for i in x:
|
||||||
for j in y:
|
for j in y:
|
||||||
c.execute('''insert into user_item values(:a,:b,:c)''', {
|
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]})
|
'a': i[0], 'b': j[0], 'c': j[1]})
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -205,91 +242,118 @@ def unlock_all_user_item(c):
|
|||||||
def unlock_user_item(c, user_id):
|
def unlock_user_item(c, user_id):
|
||||||
# 解锁用户购买
|
# 解锁用户购买
|
||||||
|
|
||||||
c.execute('''select item_id, type from item''')
|
c.execute('''select item_id, type from purchase_item''')
|
||||||
y = c.fetchall()
|
y = c.fetchall()
|
||||||
|
|
||||||
for j in y:
|
for j in y:
|
||||||
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
|
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]})
|
'a': user_id, 'b': j[0], 'c': j[1]})
|
||||||
if c.fetchone() == (0,):
|
if c.fetchone() == (0,) and j[1] != 'character':
|
||||||
c.execute('''insert into user_item values(:a,:b,:c)''', {
|
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
|
||||||
'a': user_id, 'b': j[0], 'c': j[1]})
|
'a': user_id, 'b': j[0], 'c': j[1]})
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_all_item():
|
def get_all_item():
|
||||||
# 所有购买数据查询
|
# 所有物品数据查询
|
||||||
conn = sqlite3.connect('./database/arcaea_database.db')
|
with Connect() as c:
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''select * from item''')
|
c.execute('''select * from item''')
|
||||||
x = c.fetchall()
|
x = c.fetchall()
|
||||||
re = []
|
re = []
|
||||||
if x:
|
if x:
|
||||||
for i in x:
|
for i in x:
|
||||||
discount_from = None
|
|
||||||
discount_to = None
|
|
||||||
|
|
||||||
if i[5] and i[5] >= 0:
|
|
||||||
discount_from = time.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S", time.localtime(int(i[5])/1000))
|
|
||||||
if i[6] and i[6] >= 0:
|
|
||||||
discount_to = time.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S", time.localtime(int(i[6])//1000))
|
|
||||||
|
|
||||||
re.append({'item_id': i[0],
|
re.append({'item_id': i[0],
|
||||||
'type': i[1],
|
'type': i[1],
|
||||||
'is_available': int2b(i[2]),
|
'is_available': int2b(i[2])
|
||||||
'price': i[3],
|
})
|
||||||
'orig_price': i[4],
|
|
||||||
'discount_from': discount_from,
|
return re
|
||||||
'discount_to': discount_to
|
|
||||||
|
|
||||||
|
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
|
||||||
|
discount_reason = 'Yes' if i[5] == 'anni5tix' else 'No'
|
||||||
|
|
||||||
|
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,
|
||||||
|
'discount_reason': discount_reason,
|
||||||
|
'items': items
|
||||||
})
|
})
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return re
|
return re
|
||||||
|
|
||||||
|
|
||||||
def update_one_save(c, user_id):
|
def update_one_save(c, user_id):
|
||||||
# 同步指定用户存档
|
# 同步指定用户存档
|
||||||
# 注意,best_score表不比较,直接覆盖
|
# 注意,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'])
|
|
||||||
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
|
return
|
||||||
|
|
||||||
|
# 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):
|
def update_all_save(c):
|
||||||
# 同步所有用户存档
|
# 同步所有用户存档
|
||||||
@@ -301,3 +365,178 @@ def update_all_save(c):
|
|||||||
update_one_save(c, i[0])
|
update_one_save(c, i[0])
|
||||||
|
|
||||||
return
|
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
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def get_user_score(c, user_id, limit=-1):
|
def get_user_score(c, user_id, limit=-1, offset=0):
|
||||||
# 返回用户的所有歌曲数据,带排名,返回字典列表
|
# 返回用户的所有歌曲数据,带排名,返回字典列表
|
||||||
if limit >= 0:
|
if limit >= 0:
|
||||||
c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b''',
|
c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b offset :c''',
|
||||||
{'a': user_id, 'b': limit})
|
{'a': user_id, 'b': limit, 'c': offset})
|
||||||
else:
|
else:
|
||||||
c.execute(
|
c.execute(
|
||||||
'''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id})
|
'''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id})
|
||||||
@@ -49,6 +49,10 @@ def get_user(c, user_id):
|
|||||||
if x[20]:
|
if x[20]:
|
||||||
time_played = time.strftime('%Y-%m-%d %H:%M:%S',
|
time_played = time.strftime('%Y-%m-%d %H:%M:%S',
|
||||||
time.localtime(int(x[20])//1000))
|
time.localtime(int(x[20])//1000))
|
||||||
|
if x[2] == '':
|
||||||
|
ban_flag = True
|
||||||
|
else:
|
||||||
|
ban_flag = False
|
||||||
|
|
||||||
r = {'name': x[1],
|
r = {'name': x[1],
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
@@ -65,14 +69,15 @@ def get_user(c, user_id):
|
|||||||
'time_played': time_played,
|
'time_played': time_played,
|
||||||
'clear_type': x[21],
|
'clear_type': x[21],
|
||||||
'rating': x[22],
|
'rating': x[22],
|
||||||
'ticket': x[26]
|
'ticket': x[26],
|
||||||
|
'ban_flag': ban_flag
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def get_user_recent30(c, user_id):
|
def get_user_recent30(c, user_id):
|
||||||
# 获取玩家recent30信息并计算这一部分的ptt,返回字典列表和一个值
|
# 获取玩家recent30信息并计算recent10的ptt,返回字典列表和一个值
|
||||||
c.execute('''select * from recent30 where user_id=:a''', {'a': user_id})
|
c.execute('''select * from recent30 where user_id=:a''', {'a': user_id})
|
||||||
sumr = 0
|
sumr = 0
|
||||||
x = c.fetchone()
|
x = c.fetchone()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user