Merge pull request #52 from Lost-MSth/dev

Update to v2.9
This commit is contained in:
Lost-MSth
2022-07-09 22:03:47 +08:00
committed by GitHub
66 changed files with 5630 additions and 5566 deletions

View File

@@ -21,6 +21,7 @@ This procedure is mainly used for study and research, and shall not be used for
- PTT
- 世界排名 Global rank
- 排名 Rank
- 段位系统 Course system
- Link Play
- 好友系统 Friends
- 数据同步 Data synchronization
@@ -66,23 +67,30 @@ It is just so interesting. What it can do is under exploration.
> Tips: When updating, please keep the original database in case of data loss.
### Version 2.8.5
- 适用于Arcaea 3.12.8版本 For Arcaea 3.12.8
- 更新了歌曲数据库 Update the song database.
- 修复一个导致无法升级角色的拼写错误 Fix a typing error, which makes giving characters Exp wrong.
- 尝试强制指定多进程启动方式为spawn这可能对UNIX系统中UDP服务器的启动有所帮助 Try to forcibly specify the multiprocess startup mode as spawn, which may be helpful for the startup of UDP server in UNIX system.
- 添加了注册API对外接口 Add an external API interface of user register.
- 重构一些代码,顺手修复了登陆时因多设备登录封号的用户没有正确显示错误提示的问题 Refactoring some codes and in passing fix the problem that users which has been banned because of multiple devices do not show error messages correctly when logging in.
### Version 2.9
- 适用于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`.
> 注意:
> - 现在Flask最低版本要求提高到2.0
> - 服务端可能不再完全支持低版本客户端
> - 对3.12.6c版本,愚人节开关打开时点击`网络`按钮会闪退,原因不明
> - 不再提供歌曲数据
> - 目前尽量不会对前端web页面增补功能
> - `对立`不会死
>
> Note:
> - Now the version of Flask which is required should be up to 2.0.
> - The server may no longer fully support lower version clients.
> - For version 3.12.6c, when the switch of April Fool's Day is on, clicking the `Network` button will make the client break down. The reason is not clear now.
> - No longer provide song data.
> - Since now, probably not add functions to the front-end web pages.
> - `Tairitsu` will not die.
@@ -109,8 +117,10 @@ It is just so interesting. What it can do is under exploration.
## 鸣谢 Thanks
歌曲数据库来自 Using song database from
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)
~~歌曲数据库来自 Using song database from
[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)

View 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)

View File

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

View File

@@ -1,34 +1,32 @@
from core.error import ArcError
from flask import jsonify
def code_get_msg(code):
# api接口code获取msg返回字符串
msg = {
0: '',
-1: 'See status code',
-2: 'No data',
-3: 'No data or user',
-4: 'No user_id',
-100: 'Wrong post data',
-101: 'Wrong data type',
-102: 'Wrong query parameter',
-103: 'Wrong sort parameter',
-104: 'Wrong sort order parameter',
-201: 'Wrong username or password',
-202: 'User is banned',
-203: 'Username exists',
-204: 'Email address exists',
-999: 'Unknown error'
}
return msg[code]
default_error = ArcError('Unknown Error')
def return_encode(code: int = 0, data: dict = {}, status: int = 200, msg: str = ''):
# 构造返回返回jsonify处理过后的response_class
if msg == '':
msg = code_get_msg(code)
if code < 0:
return jsonify({'status': status, 'code': code, 'data': {}, 'msg': msg})
else:
return jsonify({'status': status, 'code': code, 'data': data, 'msg': msg})
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

View File

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

View File

@@ -0,0 +1,2 @@
class Constant:
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']

View File

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

View 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()

View File

@@ -1,138 +1,150 @@
from flask import (
Blueprint, request
)
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_code import code_get_msg, return_encode
from .api_auth import role_required
from core.user import UserRegister
from core.error import ArcError, PostError
from server.sql import Connect
from server.sql import Sql
import time
import web.webscore
import server.info
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'])
def users_post(user):
# 注册用户
@request_json_handle(request, ['name', 'password', 'email'])
def users_post(data, _):
'''注册一个用户'''
with Connect() as c:
new_user = UserRegister(c)
try:
if 'name' in request.json:
new_user.set_name(request.json['name'])
else:
raise PostError('No name provided.')
if 'password' in request.json:
new_user.set_password(request.json['password'])
else:
raise PostError('No password provided.')
if 'email' in request.json:
new_user.set_email(request.json['email'])
else:
raise PostError('No email provided.')
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 return_encode(e.api_error_code)
return return_encode(0, {'user_id': new_user.user_id, 'user_code': new_user.user_code})
return error_return(e)
return error_return()
def get_users(query=None):
# 获取全用户信息,返回字典列表
r = []
@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:
x = Sql.select(c, 'user', [], query)
if x:
try:
query = Query(A, A, B).from_data(data)
x = Sql(c).select('user', query=query)
r = []
for i in x:
if i[23] != -1:
character_id = i[23]
else:
character_id = i[6]
r.append({
'user_id': i[0],
'name': i[1],
'join_date': i[3],
'user_code': i[4],
'rating_ptt': i[5]/100,
'character_id': character_id,
'is_char_uncapped': i[8] == 1,
'is_char_uncapped_override': i[9] == 1,
'is_hide_rating': i[10],
'ticket': i[26]
})
r.append(UserInfo(c).from_list(i))
return r
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()
def get_user_info(user_id):
# 获取用户信息返回字典其实就是调用user/me信息
@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)
r = {}
with Connect() as c:
r = server.info.get_user_me(c, user_id)
return r
try:
u = UserInfo(c, user_id)
return success_return(u.to_dict())
except ArcError as e:
return error_return(e)
return error_return()
def get_user_b30(user_id):
# 获取用户b30信息返回字典
@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)
r = []
with Connect() as c:
r = web.webscore.get_user_score(c, user_id, 30)
bestptt = 0
for i in r:
if i['rating']:
bestptt += i['rating']
if 'time_played' in i:
i['time_played'] = int(time.mktime(time.strptime(
i['time_played'], '%Y-%m-%d %H:%M:%S')))
return {'user_id': user_id, 'b30_ptt': bestptt / 30, 'data': r}
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()
def get_user_best(user_id, query=None):
# 获取用户b30信息返回字典
@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)
r = []
with Connect() as c:
x = Sql.select(c, 'best_score', [], query)
if x:
for i in x:
r.append({
"song_id": i[1],
"difficulty": i[2],
"score": i[3],
"shiny_perfect_count": i[4],
"perfect_count": i[5],
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": i[10],
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13]
})
return {'user_id': user_id, 'data': r}
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()
def get_user_r30(user_id):
# 获取用户r30信息返回字典
@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)
r = []
with Connect() as c:
r, r10_ptt = web.webscore.get_user_recent30(c, user_id)
return {'user_id': user_id, 'r10_ptt': r10_ptt, 'data': r}
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()

View 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))

View File

@@ -100,6 +100,8 @@ class CharacterValue:
class Character:
database_table_name = None
def __init__(self) -> None:
self.character_id = None
self.name = None
@@ -115,17 +117,45 @@ class Character:
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) -> None:
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):
# 获取此角色所需核心
@@ -137,12 +167,13 @@ class UserCharacter(Character):
for i in x:
self.uncap_cores.append(Core(i[0], i[1]))
def select_character_uncap_condition(self, user):
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': user.user_id, 'b': self.character_id})
{'a': self.user.user_id, 'b': self.character_id})
x = self.c.fetchone()
if not x:
@@ -151,12 +182,13 @@ class UserCharacter(Character):
self.is_uncapped = x[0] == 1
self.is_uncapped_override = x[1] == 1
def select_character_info(self, user):
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,
(user.user_id, self.character_id))
(self.user.user_id, self.character_id))
y = self.c.fetchone()
if y is None:
@@ -182,11 +214,13 @@ class UserCharacter(Character):
self.select_character_core()
@property
def to_dict(self):
r = {"is_uncapped_override": self.is_uncapped_override,
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,
"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,
@@ -203,29 +237,37 @@ class UserCharacter(Character):
}
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):
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': user.user_id, 'b': self.character_id})
{'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': user.user_id})
'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': user.user_id, 'c': self.character_id})
'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):
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.')
@@ -235,7 +277,7 @@ class UserCharacter(Character):
if self.is_uncapped is None:
self.c.execute(
'''select is_uncapped from user_char where user_id=? and character_id=?''', (user.user_id, self.character_id))
'''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.')
@@ -244,33 +286,37 @@ class UserCharacter(Character):
for i in self.uncap_cores:
self.c.execute(
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (user.user_id, i.item_id))
'''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(user)
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=?''',
(user.user_id, self.character_id))
(self.user.user_id, self.character_id))
self.is_uncapped = True
self.is_uncapped_override = False
def upgrade(self, user, exp_addition: float):
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(user)
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=?''', (user.user_id, self.character_id))
'''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
@@ -279,13 +325,18 @@ class UserCharacter(Character):
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, user.user_id, self.character_id))
def upgrade_by_core(self, user, core):
# parameter: user - User类或子类的实例
# core - ItemCore类或子类的实例
# 以太之滴升级注意这里core.amount应该是负数
(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.')
@@ -293,5 +344,31 @@ class UserCharacter(Character):
raise InputError(
'The amount of `core_generic` should be negative.')
core.user_claim_item(user)
self.upgrade(user, - core.amount * Constant.CORE_EXP)
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)

View File

@@ -1,3 +1,6 @@
from setting import Config
class Constant:
BAN_TIME = [1, 3, 7, 15, 31]
@@ -14,3 +17,68 @@ class Constant:
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]

View 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()

View 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)

View File

@@ -22,19 +22,19 @@ class DataExist(ArcError):
class NoData(ArcError):
# 数据不存在
def __init__(self, message=None, error_code=None, api_error_code=-2, extra_data=None) -> None:
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=None, api_error_code=-100, extra_data=None) -> None:
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=None, extra_data=None) -> None:
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)
@@ -50,6 +50,30 @@ class ItemUnavailable(ArcError):
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:
@@ -59,3 +83,8 @@ class FriendError(ArcError):
class NoAccess(ArcError):
# 无权限
pass
class Timeout(ArcError):
# 超时
pass

View File

@@ -1,5 +1,5 @@
from sympy import Nor
from .error import NoData, ItemUnavailable, ItemNotEnough, InputError
from .error import InputError, ItemNotEnough, ItemUnavailable, NoData
from setting import Config
class Item:
@@ -18,17 +18,51 @@ class Item:
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 NormalItem(Item):
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))
@@ -43,33 +77,35 @@ class NormalItem(Item):
raise NoData('No item data.')
self.c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
(user.user_id, self.item_id, self.item_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': user.user_id, 'b': self.item_id, 'c': self.item_type})
{'a': self.user.user_id, 'b': self.item_id, 'c': self.item_type})
class PositiveItem(Item):
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=?''',
(user.user_id, self.item_id, self.item_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, user.user_id, self.item_id, self.item_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(?,?,?,?)''',
(user.user_id, self.item_id, self.item_type, self.amount))
(self.user.user_id, self.item_id, self.item_type, self.amount))
class ItemCore(PositiveItem):
@@ -82,8 +118,11 @@ class ItemCore(PositiveItem):
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(Item):
class ItemCharacter(UserItem):
item_type = 'character'
def __init__(self, c) -> None:
@@ -91,7 +130,7 @@ class ItemCharacter(Item):
self.c = c
self.is_available = True
def set_id(self, character_id):
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)
@@ -105,6 +144,9 @@ class ItemCharacter(Item):
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,):
@@ -112,7 +154,7 @@ class ItemCharacter(Item):
'''insert into user_char values(?,?,1,0,0,0)''', (user.user_id, self.item_id))
class Memory(Item):
class Memory(UserItem):
item_type = 'memory'
def __init__(self, c) -> None:
@@ -131,6 +173,21 @@ class Memory(Item):
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'
@@ -144,6 +201,7 @@ class WorldSong(NormalItem):
def __init__(self, c) -> None:
super().__init__(c)
self.is_available = True
class WorldUnlock(NormalItem):
@@ -151,6 +209,18 @@ class WorldUnlock(NormalItem):
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):
@@ -167,19 +237,154 @@ class Pack(NormalItem):
super().__init__(c)
def get_user_cores(c, user) -> list:
# parameter: user - User类或子类的实例
# 得到用户的cores返回字典列表
r = []
c.execute(
'''select item_id, amount from user_item where user_id = ? and type="core"''', (user.user_id,))
x = c.fetchall()
if x:
for i in x:
if i[1]:
amount = i[1]
else:
amount = 0
r.append({'core_type': i[0], 'amount': amount})
class ProgBoost(UserItem):
item_type = 'prog_boost_300'
return r
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

View 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]

View 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)

View 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
View File

@@ -0,0 +1,157 @@
from .constant import Constant
from .score import UserScore
from .song import Chart
from .user import UserInfo
class RankList:
'''
排行榜类\
默认limit=20limit<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)

View 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
View 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.')

View File

@@ -1,40 +1,109 @@
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()
self.score = None
self.shiny_perfect_count = None
self.perfect_count = None
self.near_count = None
self.miss_count = None
self.health = None
self.modifier = None
self.time_played = None
self.best_clear_type = None
self.clear_type = None
self.rating = 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 = score
self.shiny_perfect_count = shiny_perfect_count
self.perfect_count = perfect_count
self.near_count = near_count
self.miss_count = miss_count
self.health = health
self.modifier = modifier
self.time_played = time_played
self.clear_type = clear_type
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-1视作Unrank
'''计算rating谱面定数小于等于0视为Unrank这里的defnum = Chart const'''
if not defnum or defnum <= 0:
# 谱面没定数或者定数小于等于0被视作Unrank
return -1
@@ -52,10 +121,12 @@ class Score:
def get_rating_by_calc(self) -> float:
# 通过计算得到本成绩的rating
self.rating = self.calculate_rating(self.song.defnum, self.score)
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
@property
def to_dict(self) -> dict:
return {
"rating": self.rating,
@@ -71,3 +142,430 @@ class 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]

View File

@@ -1,7 +1,81 @@
from .error import NoData
class Chart:
# defnum: chart const * 10
def __init__(self, song_id: str = None, difficulty: int = None, defnum: int = None) -> None:
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 = difficulty
self.defnum = defnum
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)

View File

@@ -1,16 +1,21 @@
import sqlite3
from flask import current_app
import traceback
from certifi import where
from flask import current_app
from .constant import Constant
from .error import InputError
class Connect():
class Connect:
# 数据库连接类,上下文管理
def __init__(self, file_path='./database/arcaea_database.db'):
def __init__(self, file_path=Constant.SQLITE_DATABASE_PATH):
"""
数据库连接默认连接arcaea_database.db
接受:文件路径
返回sqlite3连接操作对象
数据库连接默认连接arcaea_database.db\
接受:文件路径\
返回sqlite3连接操作对象
"""
self.file_path = file_path
@@ -19,7 +24,7 @@ class Connect():
self.c = self.conn.cursor()
return self.c
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
if exc_type is not None:
if self.conn:
self.conn.rollback()
@@ -34,15 +39,126 @@ class Connect():
return True
class Sql():
class Query:
'''查询参数类'''
@staticmethod
def select(c, table_name, target_column=[], query=None):
# 执行查询单句sql语句返回fetchall数据
# 使用准确查询,且在单表内
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_dict = {}
sql_list = []
if len(target_column) >= 2:
sql += target_column[0]
for i in range(1, len(target_column)):
@@ -53,34 +169,48 @@ class Sql():
else:
sql += '* from ' + table_name
where_field = []
where_value = []
if query:
for i in query.query:
where_field.append(i)
where_value.append(query.query[i])
if query is None:
self.c.execute(sql)
return self.c.fetchall()
if where_field and where_value:
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 '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 2:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
if 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 query and query.sort:
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 and query.limit >= 0:
sql += ' limit :limit offset :offset'
sql_dict['limit'] = query.limit
sql_dict['offset'] = query.offset
if query.limit >= 0:
sql += ' limit ? offset ?'
sql_list.append(query.limit)
sql_list.append(query.offset)
c.execute(sql, sql_dict)
return c.fetchall()
self.c.execute(sql, sql_list)
return self.c.fetchall()

View 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
}

View File

@@ -1,15 +1,19 @@
from .error import ArcError, InputError, DataExist, NoAccess, NoData, UserBan, FriendError
from .constant import Constant
from .character import UserCharacter
from .score import Score
from .world import Map
from .item import get_user_cores
from setting import Config
import hashlib
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
@@ -35,18 +39,22 @@ class User:
self.user_code = None
self.join_date = None
self.rating_ptt = 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
self.hash_pwd = None
def set_name(self, name: str):
if 3 <= len(name) <= 16:
@@ -63,7 +71,6 @@ class UserRegister(User):
def set_password(self, password: str):
if 8 <= len(password) <= 32:
self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
else:
raise InputError('Password is invalid.')
@@ -142,7 +149,6 @@ class UserLogin(User):
self.c = c
self.device_id = None
self.ip = None
self.hash_pwd = None
self.token = None
self.now = 0
@@ -151,7 +157,6 @@ class UserLogin(User):
def set_password(self, password: str):
self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
def set_device_id(self, device_id: str):
self.device_id = device_id
@@ -269,7 +274,7 @@ class UserAuth(User):
return self.user_id
class UserOnline(User):
class UserInfo(User):
def __init__(self, c, user_id=None) -> None:
super().__init__()
self.c = c
@@ -278,20 +283,93 @@ class UserOnline(User):
self.is_skill_sealed = False
self.is_hide_rating = False
self.recent_score = Score()
self.favorite_character = -1
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 = None
self.__friends = 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:
self.__cores = get_user_cores(self.c, self)
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:
# 得到用户的朋友列表
@@ -346,27 +424,78 @@ class UserOnline(User):
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 = self.recent_score.to_dict()
r["best_clear_type"] = best_clear_type
return [r]
def change_character(self, character_id: int, skill_sealed: bool = False):
# 用户角色改变,包括技能封印的改变
self.character = UserCharacter(self.c, character_id)
self.character.select_character_uncap_condition(self)
self.is_skill_sealed = skill_sealed
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))
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})
@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 select_user(self):
# 查user表所有信息
self.c.execute(
'''select * from user where user_id = :x''', {'x': self.user_id})
x = self.c.fetchone()
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:
raise NoData('No user.', 108, -3)
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]
@@ -386,19 +515,184 @@ class UserOnline(User):
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])
self.current_map = Map(x[25]) if x[25] is not None else Map('')
self.ticket = x[26]
self.prog_boost = x[27]
self.email = x[28]
self.world_rank_score = x[29]
self.ban_flag = x[30]
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]
self.max_stamina_ts = x[32]
self.stamina = x[33]
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)
@@ -411,8 +705,7 @@ class UserOnline(User):
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,):
@@ -420,3 +713,9 @@ class UserOnline(User):
{'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})

View 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()

View File

@@ -1,3 +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 = map_id
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')

Binary file not shown.

View 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"
]
}
]

View File

@@ -4,7 +4,7 @@ import json
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
ARCAEA_SERVER_VERSION = 'v2.8.6'
ARCAEA_SERVER_VERSION = 'v2.9.0'
def main(path='./'):
@@ -48,7 +48,8 @@ def main(path='./'):
ban_flag text,
next_fragstam_ts int,
max_stamina_ts int,
stamina int
stamina int,
world_mode_locked_end_ts int
);''')
c.execute('''create table if not exists login(access_token text,
user_id int,
@@ -187,13 +188,17 @@ def main(path='./'):
is_locked int,
primary key(user_id, map_id)
);''')
c.execute('''create table if not exists world_songplay(user_id int,
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,
primary key(user_id, song_id, difficulty)
prog_boost_multiply int
);''')
c.execute('''create table if not exists download_token(user_id int,
song_id text,
@@ -240,7 +245,8 @@ def main(path='./'):
installid_data text,
devicemodelname_data text,
story_data text,
createdAt int
createdAt int,
finalestate_data text
);''')
c.execute('''create table if not exists present(present_id text primary key,
expire_ts int,
@@ -256,10 +262,12 @@ def main(path='./'):
amount int,
primary key(present_id, item_id, type)
);''')
c.execute('''create table if not exists songfile(song_id text,
file_type int,
md5 text,
primary key(song_id, file_type)
c.execute('''create table if not exists 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
@@ -297,49 +305,80 @@ def main(path='./'):
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)
);''')
# 搭档初始化
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']
'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)']
skill_id = ['gauge_easy', '', '', '', 'note_mirror', '', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita']
'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']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
'', '', '', '', '', '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, 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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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}],
@@ -360,7 +399,7 @@ def main(path='./'):
19: [{'core_id': 'core_colorful', 'amount': 30}]
}
for i in range(0, 55):
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]:
@@ -368,10 +407,11 @@ def main(path='./'):
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
else:
if i != 5:
sql = '''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
if i == 5:
continue
sql = '''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
c.execute('''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)''', (99,
'shirahime', 38, 33, 28, 66, 58, 50, 66, 58, 50, 'frags_preferred_song', 0, 0, '', 0))
@@ -381,7 +421,7 @@ def main(path='./'):
c.execute('''insert into char_item values(?,?,'core',?)''',
(i, j['core_id'], j['amount']))
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful']
'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,))
@@ -392,10 +432,14 @@ def main(path='./'):
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_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(?,?,?,?)''',
@@ -438,17 +482,41 @@ def main(path='./'):
(i['name'], j['id'], j['type'], amount))
# item初始化
f = open(path+'singles.json', 'r')
singles = json.load(f)
f.close()
insert_items(c, singles)
try:
f = open(path+'singles.json', 'r')
singles = json.load(f)
f.close()
insert_items(c, singles)
except:
pass
f = open(path+'packs.json', 'r')
packs = json.load(f)
f.close()
insert_items(c, packs)
try:
f = open(path+'packs.json', 'r')
packs = json.load(f)
f.close()
insert_items(c, packs)
except:
pass
# api权限与权限组初始化
# course初始化
try:
f = open(path+'courses.json', 'r', encoding="utf-8")
courses = json.load(f)
f.close()
except:
courses = []
if courses:
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 = ['管理员', '用户', '查询接口']

View File

@@ -41,8 +41,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "dynamix",
@@ -66,8 +66,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "mirai",
@@ -91,8 +91,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "yugamu",
@@ -111,8 +111,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "lanota",
@@ -131,8 +131,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "nijuusei",
@@ -171,8 +171,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "tonesphere",
@@ -191,8 +191,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "groovecoaster",
@@ -211,8 +211,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "zettai",
@@ -231,8 +231,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "chunithm",
@@ -269,8 +269,8 @@
],
"price": 400,
"orig_price": 400,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "omatsuri",
@@ -289,8 +289,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "vs",
@@ -309,8 +309,8 @@
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "extend",
@@ -329,8 +329,9 @@
],
"price": 0,
"orig_price": 700,
"discount_from": 1615248000000,
"discount_to": 1615852799000
"discount_from": 1646784000000,
"discount_to": 1647388799000,
"discount_reason": "extend"
},
{
"name": "alice",
@@ -348,7 +349,9 @@
}
],
"orig_price": 500,
"price": 500
"price": 500,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "alice_append_1",
@@ -366,7 +369,9 @@
}
],
"orig_price": 300,
"price": 300
"price": 300,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "ongeki",
@@ -384,7 +389,9 @@
}
],
"orig_price": 400,
"price": 400
"price": 400,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "maimai",
@@ -402,7 +409,9 @@
}
],
"orig_price": 400,
"price": 400
"price": 400,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "chunithm_append_1",
@@ -438,7 +447,9 @@
}
],
"orig_price": 300,
"price": 300
"price": 300,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "observer",
@@ -456,7 +467,9 @@
}
],
"orig_price": 500,
"price": 500
"price": 500,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "observer_append_2",
@@ -474,7 +487,9 @@
}
],
"orig_price": 300,
"price": 300
"price": 300,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "wacca",
@@ -492,7 +507,9 @@
}
],
"orig_price": 500,
"price": 500
"price": 500,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "nijuusei_append_1",
@@ -528,7 +545,9 @@
}
],
"orig_price": 500,
"price": 500
"price": 500,
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "musedash",
@@ -538,20 +557,17 @@
"id": "musedash",
"is_available": true
},
{
"type": "character",
"id": "marija",
"is_available": true
},
{
"type": "core",
"amount": 5,
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400,
"orig_price": 400
"discount_from": 1646784000000,
"discount_to": 1647388799000
},
{
"name": "lanota_append_1",
@@ -570,5 +586,23 @@
],
"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
}
]

View File

@@ -1,30 +1,23 @@
# encoding: utf-8
from flask import Flask, json, request, jsonify, send_from_directory
from logging.config import dictConfig
from setting import Config
import server
import server.auth
import server.info
import server.setme
import server.arcscore
import web.login
import web.index
import api.api_main
import server.arcworld
import server.arcdownload
import server.arcpurchase
import server.init
import server.character
import server.arclinkplay
import os
import sys
from multiprocessing import Process, Pipe, set_start_method
from logging.config import dictConfig
from multiprocessing import Process, set_start_method
from flask import Flask, request, send_from_directory
from urllib.parse import parse_qs, urlparse
from werkzeug.datastructures import ImmutableMultiDict
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__)
wsgi_app = app.wsgi_app
@@ -35,103 +28,9 @@ app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.bp)
app.register_blueprint(api.bp)
app.register_blueprint(server.bp)
conn1, conn2 = Pipe()
def add_url_prefix(url, strange_flag=False):
# 给url加前缀返回字符串
if not url or not Config.GAME_API_PREFIX:
return Config.GAME_API_PREFIX + url
prefix = Config.GAME_API_PREFIX
if prefix[0] != '/':
prefix = '/' + prefix
if prefix[-1] == '/':
prefix = prefix[:-1]
if url[0] != '/':
r = '/' + url
else:
r = url
if strange_flag and prefix.count('/') >= 1: # 为了方便处理双斜杠
t = prefix[::-1]
t = t[t.find('/')+1:]
prefix = t[::-1]
return prefix + r
def error_return(error_code, extra={}): # 错误返回
# -7 处理交易时发生了错误
# -5 所有的曲目都已经下载完毕
# -4 您的账号已在别处登录
# -3 无法连接至服务器
# 2 Arcaea服务器正在维护
# 9 新版本请等待几分钟
# 100 无法在此ip地址下登录游戏
# 101 用户名占用
# 102 电子邮箱已注册
# 103 已有一个账号由此设备创建
# 104 用户名密码错误
# 105 24小时内登入两台设备
# 106 121 账户冻结
# 107 你没有足够的体力
# 113 活动已结束
# 114 该活动已结束,您的成绩不会提交
# 115 请输入有效的电子邮箱地址
# 120 封号警告
# 122 账户暂时冻结
# 123 账户被限制
# 124 你今天不能再使用这个IP地址创建新的账号
# 150 非常抱歉您已被限制使用此功能
# 151 目前无法使用此功能
# 401 用户不存在
# 403 无法连接至服务器
# 501 502 -6 此物品目前无法获取
# 504 无效的序列码
# 505 此序列码已被使用
# 506 你已拥有了此物品
# 601 好友列表已满
# 602 此用户已是好友
# 604 你不能加自己为好友
# 903 下载量超过了限制请24小时后重试
# 905 请在再次使用此功能前等待24小时
# 1001 设备数量达到上限
# 1002 此设备已使用过此功能
# 1201 房间已满
# 1202 房间号码无效
# 1203 请将Arcaea更新至最新版本
# 1205 此房间目前无法加入
# 9801 下载歌曲时发生问题,请再试一次
# 9802 保存歌曲时发生问题,请检查设备空间容量
# 9803 下载已取消
# 9905 没有在云端发现任何数据
# 9907 更新数据时发生了问题
# 9908 服务器只支持最新的版本请更新Arcaea
# 其它 发生未知错误
if extra:
return jsonify({
"success": False,
"error_code": error_code,
"extra": extra
})
else:
return jsonify({
"success": False,
"error_code": error_code
})
def success_return(value):
return jsonify({
"success": True,
"value": value
})
@app.route('/')
def hello():
@@ -147,508 +46,29 @@ def favicon():
return app.send_static_file('favicon.ico')
@app.route(add_url_prefix('/purchase/bundle/pack'), methods=['GET']) # 曲包信息
@server.auth.auth_required(request)
def bundle_pack(user_id):
return success_return(server.info.get_purchase_pack(user_id))
@app.route(add_url_prefix('/game/info'), methods=['GET']) # 系统信息
def game_info():
return success_return(server.info.get_game_info())
@app.route(add_url_prefix('/present/me'), methods=['GET']) # 用户奖励信息
@server.auth.auth_required(request)
def present_info(user_id):
return success_return(server.info.get_user_present(user_id))
@app.route(add_url_prefix('/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(108)
for i in get_list:
endpoint = i['endpoint']
url = add_url_prefix(endpoint)
request.args = ImmutableMultiDict(
{key: value[0] for key, value in parse_qs(urlparse(url).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(108)
# # 集成式请求,没想到什么好办法处理,就先这样写着
# @app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
# @server.auth.auth_required(request)
# def aggregate(user_id):
# calls = request.args.get('calls')
# if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
# r = server.info.arc_aggregate_small(user_id)
# else:
# r = server.info.arc_aggregate_big(user_id)
# return jsonify(r)
@app.route(add_url_prefix('/user/me'), methods=['GET']) # 用户信息
@server.auth.auth_required(request)
def user_me(user_id):
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
# 好友排名默认最多50
@app.route(add_url_prefix('/score/song/friend'), methods=['GET'])
@server.auth.auth_required(request)
def song_score_friend(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_friend(user_id, song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song/me'), methods=['GET']) # 我的排名默认最多20
@server.auth.auth_required(request)
def song_score_me(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_me(user_id, song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song'), methods=['GET']) # TOP20
@server.auth.auth_required(request)
def song_score_top(user_id):
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
r = server.arcscore.arc_score_top(song_id, difficulty)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/score/song'), methods=['POST']) # 成绩上传
@server.auth.auth_required(request)
def song_score_post(user_id):
song_token = request.form['song_token']
song_hash = request.form['song_hash']
song_id = request.form['song_id']
difficulty = int(request.form['difficulty'])
score = int(request.form['score'])
shiny_perfect_count = int(request.form['shiny_perfect_count'])
perfect_count = int(request.form['perfect_count'])
near_count = int(request.form['near_count'])
miss_count = int(request.form['miss_count'])
health = int(request.form['health'])
modifier = int(request.form['modifier'])
beyond_gauge = int(request.form['beyond_gauge'])
clear_type = int(request.form['clear_type'])
submission_hash = request.form['submission_hash']
# 增加成绩校验
if not server.arcscore.arc_score_check(user_id, song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type, song_token, song_hash, submission_hash):
return error_return(107)
r, re = server.arcscore.arc_score_post(user_id, song_id, difficulty, score, shiny_perfect_count,
perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type)
if re:
return jsonify({
"success": True,
"value": re
})
else:
return error_return(108)
# 成绩上传所需的token显然我不想验证
@app.route(add_url_prefix('/score/token'), methods=['GET'])
def score_token():
return jsonify({
"success": True,
"value": {
"token": "1145141919810"
}
})
# 世界模式成绩上传所需的token无验证
@app.route(add_url_prefix('/score/token/world'), methods=['GET'])
@server.auth.auth_required(request)
def score_token_world(user_id):
args = request.args
r = server.arcworld.play_world_song(user_id, args)
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/user/me/save'), methods=['GET']) # 从云端同步
@server.auth.auth_required(request)
def cloud_get(user_id):
r = server.arcscore.arc_all_get(user_id)
if r is not None:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/user/me/save'), methods=['POST']) # 向云端同步
@server.auth.auth_required(request)
def cloud_post(user_id):
scores_data = request.form['scores_data']
clearlamps_data = request.form['clearlamps_data']
clearedsongs_data = request.form['clearedsongs_data']
unlocklist_data = request.form['unlocklist_data']
installid_data = request.form['installid_data']
devicemodelname_data = request.form['devicemodelname_data']
story_data = request.form['story_data']
server.arcscore.arc_all_post(user_id, scores_data, clearlamps_data, clearedsongs_data,
unlocklist_data, installid_data, devicemodelname_data, story_data)
return jsonify({
"success": True,
"value": {
"user_id": user_id
}
})
@app.route(add_url_prefix('/purchase/me/redeem'), methods=['POST']) # 兑换码
@server.auth.auth_required(request)
def redeem(user_id):
code = request.form['code']
fragment, error_code = server.arcpurchase.claim_user_redeem(
user_id, code)
if not error_code:
if fragment > 0:
return jsonify({
"success": True,
"value": {"coupon": "fragment"+str(fragment)}
})
else:
return jsonify({
"success": True,
"value": {"coupon": ""}
})
else:
return error_return(error_code)
# 礼物确认
@app.route(add_url_prefix('/present/me/claim/<present_id>'), methods=['POST'])
@server.auth.auth_required(request)
def claim_present(user_id, present_id):
flag = server.arcpurchase.claim_user_present(user_id, present_id)
if flag:
return jsonify({
"success": True
})
else:
return error_return(108)
# 购买体力
@app.route(add_url_prefix('/purchase/me/stamina/<buy_stamina_type>'), methods=['POST'])
@server.auth.auth_required(request)
def purchase_stamina(user_id, buy_stamina_type):
if buy_stamina_type == 'fragment':
r, error_code = server.arcworld.buy_stamina_by_fragment(user_id)
else:
return error_return(108)
if error_code:
return error_return(error_code)
else:
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
# 购买world模式boost
@app.route(add_url_prefix('/purchase/me/item'), methods=['POST'])
@server.auth.auth_required(request)
def prog_boost(user_id):
re = {"success": False}
if 'item_id' in request.form:
if request.form['item_id'] == 'prog_boost_300':
ticket, error_code = server.arcpurchase.get_prog_boost(user_id)
if error_code:
return error_return(error_code)
re = {
"success": True,
"value": {'ticket': ticket}
}
elif request.form['item_id'] == 'stamina6':
r, error_code = server.arcworld.buy_stamina_by_ticket(user_id)
if error_code:
return error_return(error_code)
re = {
"success": True,
"value": r
}
return jsonify(re)
@app.route(add_url_prefix('/purchase/me/pack'), methods=['POST']) # 曲包和单曲购买
@server.auth.auth_required(request)
def pack(user_id):
if 'pack_id' in request.form:
return jsonify(server.arcpurchase.buy_thing(user_id, request.form['pack_id']))
if 'single_id' in request.form:
return jsonify(server.arcpurchase.buy_thing(user_id, request.form['single_id']))
return jsonify({"success": True})
# 单曲购买信息获取
@app.route(add_url_prefix('/purchase/bundle/single'), methods=['GET'])
@server.auth.auth_required(request)
def single(user_id):
return jsonify({
"success": True,
"value": server.arcpurchase.get_single_purchase(user_id)
})
@app.route(add_url_prefix('/world/map/me'), methods=['GET']) # 获得世界模式信息,所有地图
@server.auth.auth_required(request)
def world_all(user_id):
return jsonify({
"success": True,
"value": {
"current_map": server.arcworld.get_current_map(user_id),
"user_id": user_id,
"maps": server.arcworld.get_world_all(user_id)
}
})
@app.route(add_url_prefix('/world/map/me'), methods=['POST']) # 进入地图
@server.auth.auth_required(request)
def world_in(user_id):
map_id = request.form['map_id']
flag = server.arcworld.unlock_user_world(user_id, map_id)
return jsonify({
"success": flag,
"value": server.arcworld.get_user_world(user_id, map_id)
})
# 获得单个地图完整信息
@app.route(add_url_prefix('/world/map/me/<map_id>'), methods=['GET'])
@server.auth.auth_required(request)
def world_one(user_id, map_id):
server.arcworld.change_user_current_map(user_id, map_id)
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"current_map": map_id,
"maps": [server.arcworld.get_user_world_info(user_id, map_id)]
}
})
@app.route(add_url_prefix('/serve/download/me/song'), methods=['GET']) # 歌曲下载
@server.auth.auth_required(request)
def download_song(user_id):
song_ids = request.args.getlist('sid')
url_flag = json.loads(request.args.get('url', 'true'))
if server.arcdownload.is_able_download(user_id) or not url_flag:
re = {}
if not song_ids:
re = server.arcdownload.get_all_songs(user_id, url_flag=url_flag)
else:
re = server.arcdownload.get_some_songs(user_id, song_ids)
return jsonify({
"success": True,
"value": re
})
else:
return error_return(903)
@app.route('/download/<path:file_path>', methods=['GET']) # 下载
def download(file_path):
try:
t = request.args.get('t')
message = server.arcdownload.is_token_able_download(t, file_path)
if message == 0:
path = os.path.join('./database/songs', file_path)
if os.path.isfile(path) and not('../' in path or '..\\' in path):
return send_from_directory('./database/songs', file_path, as_attachment=True)
else:
return error_return(109)
else:
return error_return(message)
except:
return error_return(108)
with Connect() as c:
try:
x = UserDownload(c)
x.file_path = file_path
x.select_from_token(request.args.get('t'))
if x.is_limited:
raise ArcError('You have reached the download limit.', 903)
if x.is_valid:
x.insert_user_download()
return send_from_directory(Constant.SONG_FILE_FOLDER_PATH, file_path, as_attachment=True)
except ArcError as e:
return error_return(e)
return error_return()
# 创建房间
@app.route(add_url_prefix('/multiplayer/me/room/create'), methods=['POST'])
@server.auth.auth_required(request)
def room_create(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.create_room(
conn1, user_id, client_song_map)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
def tcp_server_run():
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
return error_return(error_code), 400
# 加入房间
@app.route(add_url_prefix('/multiplayer/me/room/join/<room_code>'), methods=['POST'])
@server.auth.auth_required(request)
def room_join(user_id, room_code):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.join_room(
conn1, user_id, client_song_map, room_code)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
@app.route(add_url_prefix('/multiplayer/me/update'), methods=['POST']) # 更新房间
@server.auth.auth_required(request)
def multiplayer_update(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
token = request.json['token']
error_code, value = server.arclinkplay.update_room(conn1, user_id, token)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
# @app.route(add_url_prefix('/user/me/request_delete'), methods=['POST']) # 删除账号
# @server.auth.auth_required(request)
# def user_delete(user_id):
# return error_return(151), 404
# 三个设置,写在最后降低优先级
@app.route(add_url_prefix('/<path:path>', True), methods=['POST'])
@server.auth.auth_required(request)
def sys_set(user_id, path):
set_arg = path[5:]
value = request.form['value']
server.setme.arc_sys_set(user_id, value, set_arg)
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
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}
app.run(Config.HOST, Config.PORT)
def main():
@@ -699,34 +119,24 @@ def main():
input('Press ENTER key to exit.')
sys.exit()
app.logger.info("Start to initialize data in 'songfile' table...")
app.logger.info("Start to initialize song data...")
try:
error = server.arcdownload.initialize_songfile()
except:
error = 'Something wrong.'
if error:
app.logger.warning(error)
else:
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...")
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
tcp_server_run()
[p.join() for p in process]
else:
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
tcp_server_run()
if __name__ == '__main__':

View File

@@ -3,8 +3,22 @@ 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)

View File

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

View File

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

View File

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

View File

@@ -1,202 +1,4 @@
from server.config import Constant
from server.sql import Connect
import time
import json
import server.arcworld
import hashlib
from setting import Config
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 md5(code):
# md5加密算法
code = code.encode()
md5s = hashlib.md5()
md5s.update(code)
codes = md5s.hexdigest()
return codes
def get_score(c, user_id, song_id, difficulty):
# 根据user_id、song_id、难度得到该曲目最好成绩返回字典
c.execute('''select * from best_score where user_id = :a and song_id = :b and difficulty = :c''',
{'a': user_id, 'b': song_id, 'c': difficulty})
x = c.fetchone()
if x is not None:
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': user_id})
y = c.fetchone()
if y is not None:
character = y[1]
is_char_uncapped = int2b(y[3])
if y[5] != -1:
character = y[5]
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id=:a and character_id=:b''', {
'a': user_id, 'b': character})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id=:a and character_id=:b''', {
'a': user_id, 'b': character})
z = c.fetchone()
if z:
if z[1] == 0:
is_char_uncapped = int2b(z[0])
else:
is_char_uncapped = False
else:
if y[4] == 1:
is_char_uncapped = False
return {
"user_id": x[0],
"song_id": x[1],
"difficulty": x[2],
"score": x[3],
"shiny_perfect_count": x[4],
"perfect_count": x[5],
"near_count": x[6],
"miss_count": x[7],
"health": x[8],
"modifier": x[9],
"time_played": x[10],
"best_clear_type": x[11],
"clear_type": x[12],
"name": y[0],
"character": character,
"is_skill_sealed": int2b(y[2]),
"is_char_uncapped": is_char_uncapped
}
else:
return {}
else:
return {}
def arc_score_friend(user_id, song_id, difficulty, limit=50):
# 得到用户好友分数表默认最大50个
r = []
with Connect() as c:
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': user_id, 'song_id': song_id, 'difficulty': difficulty, 'limit': limit})
x = c.fetchall()
if x != []:
rank = 0
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
return r
def arc_score_top(song_id, difficulty, limit=20):
# 得到top分数表默认最多20个如果是负数则全部查询
r = []
with Connect() as c:
if limit >= 0:
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': song_id, 'difficulty': difficulty, 'limit': limit})
else:
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': song_id, 'difficulty': difficulty})
x = c.fetchall()
if x != []:
rank = 0
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
return r
def arc_score_me(user_id, song_id, difficulty, limit=20):
# 得到用户的排名默认最大20个
r = []
with Connect() as c:
c.execute('''select exists(select * from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty)''', {
'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty})
if c.fetchone() == (1,):
c.execute('''select count(*) from best_score where song_id = :song_id and difficulty = :difficulty and (score>(select score from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty) or (score>(select score from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty) and time_played > (select time_played from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty)) )''', {
'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty})
x = c.fetchone()
myrank = int(x[0]) + 1
c.execute('''select count(*) from best_score where song_id=:a and difficulty=:b''',
{'a': song_id, 'b': difficulty})
amount = int(c.fetchone()[0])
if myrank <= 4: # 排名在前4
return arc_score_top(song_id, difficulty, limit)
elif myrank >= 5 and myrank <= 9999 - limit + 4 and amount >= 10000: # 万名内前面有4个人
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': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': myrank - 5})
x = c.fetchall()
if x != []:
rank = myrank - 5
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
elif myrank >= 10000: # 万名外
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': song_id, 'difficulty': difficulty, 'limit': limit - 1, 'offset': 9999-limit})
x = c.fetchall()
if x != []:
rank = 9999 - limit
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
y = get_score(c, user_id, song_id, difficulty)
y['rank'] = -1
r.append(y)
elif amount - myrank < limit - 5: # 后方人数不足
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': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': amount - limit})
x = c.fetchall()
if x != []:
rank = amount - limit
if rank < 0:
rank = 0
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
else:
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': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': 9998-limit})
x = c.fetchall()
if x != []:
rank = 9998 - limit
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
return r
from core.sql import Connect
def calculate_rating(defnum, score):
@@ -213,349 +15,13 @@ def calculate_rating(defnum, score):
return ptt
def get_one_ptt(song_id, difficulty, score: int) -> float:
# 单曲ptt计算ptt为负说明没谱面定数数据
ptt = -10
with Connect('./database/arcsong.db') as c:
if difficulty == 0:
c.execute('''select rating_pst from songs where sid = :sid;''', {
'sid': song_id})
elif difficulty == 1:
c.execute('''select rating_prs from songs where sid = :sid;''', {
'sid': song_id})
elif difficulty == 2:
c.execute('''select rating_ftr from songs where sid = :sid;''', {
'sid': song_id})
elif difficulty == 3:
c.execute('''select rating_byn from songs where sid = :sid;''', {
'sid': song_id})
x = c.fetchone()
defnum = -10 # 没在库里的全部当做定数-10
if x is not None and x != '':
defnum = float(x[0]) / 10
if defnum <= 0:
defnum = -10 # 缺少难度的当做定数-10
ptt = calculate_rating(defnum, score)
return ptt
def get_song_grade(x):
# 成绩转换评级
if x >= 9900000: # EX+
return 6
elif x < 9900000 and x >= 9800000: # EX
return 5
elif x < 9800000 and x >= 9500000: # AA
return 4
elif x < 9500000 and x >= 9200000: # A
return 3
elif x < 9200000 and x >= 8900000: # B
return 2
elif x < 8900000 and x >= 8600000: # C
return 1
else:
return 0
def get_song_state(x):
# 返回成绩状态,便于比较
if x == 3: # PM
return 5
elif x == 2: # FC
return 4
elif x == 5: # Hard Clear
return 3
elif x == 1: # Clear
return 2
elif x == 4: # Easy Clear
return 1
else: # Track Lost
return 0
def get_user_ptt_float(c, user_id) -> float:
# 总ptt计算返回浮点数
sumr = 0
c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', {
'a': user_id})
x = c.fetchall()
if not Config.USE_B10_AS_R10:
if x != []:
for i in x:
sumr += float(i[0])
c.execute('''select * from recent30 where user_id = :a''',
{'a': user_id})
x = c.fetchone()
if x is not None:
r30 = []
s30 = []
for i in range(1, 61, 2):
if x[i] is not None:
r30.append(float(x[i]))
s30.append(x[i+1])
else:
r30.append(0)
s30.append('')
r30, s30 = (list(t)
for t in zip(*sorted(zip(r30, s30), reverse=True)))
songs = []
i = 0
while len(songs) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None:
if s30[i] not in songs:
sumr += r30[i]
songs.append(s30[i])
i += 1
else:
if x != []:
for i in range(len(x)):
t = float(x[i][0])
sumr += t
if i < 10:
sumr += t
return sumr/40
def get_user_ptt(c, user_id) -> int:
# 总ptt计算返回4位整数向下取整
return int(get_user_ptt_float(c, user_id)*100)
def get_user_world_rank(c, user_id) -> int:
# 用户世界排名计算同时返回排名值如果超过设定最大值返回0
with Connect('./database/arcsong.db') as c2:
c2.execute(
'''select sid, rating_ftr, rating_byn from songs''')
x = c2.fetchall()
if x:
song_list_ftr = [user_id]
song_list_byn = [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:
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 = c.fetchone()
if x[0] is not None:
score_sum = x[0]
else:
score_sum = 0
if len(song_list_byn) >= 2:
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 = c.fetchone()
if x[0] is not None:
score_sum += x[0]
else:
score_sum += 0
c.execute('''update user set world_rank_score = :b where user_id = :a''', {
'a': user_id, 'b': score_sum})
c.execute(
'''select count(*) from user where world_rank_score > ?''', (score_sum,))
x = c.fetchone()
if x and x[0] + 1 <= Config.WORLD_RANK_MAX:
return x[0] + 1
else:
return 0
def update_recent30(c, user_id, song_id, rating, is_protected):
# 刷新r30这里的判断方法存疑这里的song_id结尾包含难度数字
def insert_r30table(c, user_id, a, b):
# 更新r30表
c.execute('''delete from recent30 where user_id = :a''',
{'a': user_id})
sql = 'insert into recent30 values(' + str(user_id)
for i in range(0, 30):
if a[i] is not None and b[i] is not None:
sql = sql + ',' + str(a[i]) + ',"' + b[i] + '"'
else:
sql = sql + ',0,""'
sql = sql + ')'
c.execute(sql)
c.execute('''select * from recent30 where user_id = :a''', {'a': user_id})
x = c.fetchone()
if not x:
x = [None] * 61
x[0] = user_id
for i in range(2, 61, 2):
x[i] = ''
songs = []
flag = True
for i in range(2, 61, 2):
if x[i] is None or x[i] == '':
r30_id = 29
flag = False
break
if x[i] not in songs:
songs.append(x[i])
if flag:
n = len(songs)
if n >= 11:
r30_id = 29
elif song_id not in songs and n == 10:
r30_id = 29
elif song_id in songs and n == 10:
i = 29
while x[i*2+2] == song_id:
i -= 1
r30_id = i
elif song_id not in songs and n == 9:
i = 29
while x[i*2+2] == song_id:
i -= 1
r30_id = i
else:
r30_id = 29
a = []
b = []
for i in range(1, 61, 2):
a.append(x[i])
b.append(x[i+1])
if is_protected:
ptt_pre = get_user_ptt_float(c, user_id)
a_pre = [x for x in a]
b_pre = [x for x in b]
for i in range(r30_id, 0, -1):
a[i] = a[i-1]
b[i] = b[i-1]
a[0] = rating
b[0] = song_id
insert_r30table(c, user_id, a, b)
if is_protected:
ptt = get_user_ptt_float(c, user_id)
if ptt < ptt_pre:
# 触发保护
if song_id in b_pre:
for i in range(29, -1, -1):
if song_id == b_pre[i] and rating > a_pre[i]:
# 发现重复歌曲更新到最高rating
a_pre[i] = rating
break
insert_r30table(c, user_id, a_pre, b_pre)
return None
def arc_score_post(user_id, song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type):
# 分数上传返回变化后的ptt和世界模式变化
ptt = None
re = None
with Connect() as c:
rating = get_one_ptt(song_id, difficulty, score)
if rating < 0: # 没数据不会向recent30里记入
unrank_flag = True
rating = 0
else:
unrank_flag = False
now = int(time.time() * 1000)
# recent 更新
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': user_id, 'b': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': clear_type, 'l': rating, 'm': now})
# 成绩录入
c.execute('''select score, best_clear_type from best_score where user_id = :a and song_id = :b and difficulty = :c''', {
'a': user_id, 'b': song_id, 'c': difficulty})
now = int(now // 1000)
x = c.fetchone()
if x is None:
first_protect_flag = True # 初见保护
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': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': now, 'l': clear_type, 'm': clear_type, 'n': rating})
else:
first_protect_flag = False
if get_song_state(clear_type) > get_song_state(int(x[1])): # 状态更新
c.execute('''update best_score set best_clear_type = :a where user_id = :b and song_id = :c and difficulty = :d''', {
'a': clear_type, 'b': user_id, 'c': song_id, 'd': difficulty})
if score >= int(x[0]): # 成绩更新
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': user_id, 'b': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': clear_type, 'l': rating, 'm': now})
if not unrank_flag:
# recent30 更新
if health == -1 or int(score) >= 9800000 or first_protect_flag:
update_recent30(c, user_id, song_id +
str(difficulty), rating, True)
else:
update_recent30(c, user_id, song_id +
str(difficulty), rating, False)
# 总PTT更新
ptt = get_user_ptt(c, user_id)
c.execute('''update user set rating_ptt = :a where user_id = :b''', {
'a': ptt, 'b': user_id})
# 世界模式判断
c.execute('''select stamina_multiply,fragment_multiply,prog_boost_multiply from world_songplay where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': song_id, 'c': difficulty})
x = c.fetchone()
if x:
re = server.arcworld.world_update(
c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, health, x[0], x[1], x[2])
re['global_rank'] = get_user_world_rank(c, user_id) # 更新世界排名
re["user_rating"] = ptt
else:
re = {'global_rank': get_user_world_rank(
c, user_id), 'user_rating': ptt}
return ptt, re
def 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):
# 分数校验,返回布尔值
if shiny_perfect_count < 0 or perfect_count < 0 or near_count < 0 or miss_count < 0 or score < 0:
return False
if difficulty not in [0, 1, 2, 3]:
return False
all_note = perfect_count + near_count + miss_count
ascore = 10000000 / all_note * \
(perfect_count + near_count/2) + shiny_perfect_count
if abs(ascore - score) >= 5:
return False
with Connect() as c: # 歌曲谱面MD5检查服务器没有谱面就不管了
c.execute('''select md5 from songfile where song_id=:a and file_type=:b''', {
'a': song_id, 'b': int(difficulty)})
x = c.fetchone()
if x:
if x[0] != song_hash:
return False
x = song_token + song_hash + song_id + str(difficulty) + str(score) + str(shiny_perfect_count) + str(
perfect_count) + str(near_count) + str(miss_count) + str(health) + str(modifier) + str(clear_type)
y = str(user_id) + song_hash
checksum = md5(x+md5(y))
if checksum != submission_hash:
return False
return True
def refresh_all_score_rating():
# 刷新所有best成绩的rating
error = 'Unknown error.'
with Connect('./database/arcsong.db') as c:
with Connect() as c:
c.execute(
'''select sid, rating_pst, rating_prs, rating_ftr, rating_byn from songs''')
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
x = c.fetchall()
if x:
@@ -588,76 +54,3 @@ def refresh_all_score_rating():
error = 'No song data.'
return error
def arc_all_post(user_id, scores_data, clearlamps_data, clearedsongs_data, unlocklist_data, installid_data, devicemodelname_data, story_data):
# 向云端同步,无返回
with Connect() as c:
now = int(time.time() * 1000)
c.execute('''delete from user_save where user_id=:a''', {'a': user_id})
c.execute('''insert into user_save values(:a,:b,:c,:d,:e,:f,:g,:h,:i)''', {
'a': user_id, 'b': scores_data, 'c': clearlamps_data, 'd': clearedsongs_data, 'e': unlocklist_data, 'f': installid_data, 'g': devicemodelname_data, 'h': story_data, 'i': now})
return None
def arc_all_get(user_id):
# 从云端同步,返回字典
scores_data = []
clearlamps_data = []
clearedsongs_data = []
unlocklist_data = []
installid_data = ''
devicemodelname_data = ''
story_data = []
createdAt = 0
with Connect() as c:
c.execute('''select * from user_save where user_id=:a''',
{'a': user_id})
x = c.fetchone()
if x:
scores_data = json.loads(x[1])[""]
clearlamps_data = json.loads(x[2])[""]
clearedsongs_data = json.loads(x[3])[""]
unlocklist_data = json.loads(x[4])[""]
installid_data = json.loads(x[5])["val"]
devicemodelname_data = json.loads(x[6])["val"]
story_data = json.loads(x[7])[""]
if x[8]:
createdAt = int(x[8])
if Config.SAVE_FULL_UNLOCK:
installid_data = "0fcec8ed-7b62-48e2-9d61-55041a22b123"
story_data = Constant.story_data
unlocklist_data = Constant.unlocklist_data
return {
"user_id": user_id,
"story": {
"": story_data
},
"devicemodelname": {
"val": devicemodelname_data
},
"installid": {
"val": installid_data
},
"unlocklist": {
"": unlocklist_data
},
"clearedsongs": {
"": clearedsongs_data
},
"clearlamps": {
"": clearlamps_data
},
"scores": {
"": scores_data
},
"version": {
"val": 1
},
"createdAt": createdAt
}

View File

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

View File

@@ -1,12 +1,13 @@
from flask import Blueprint, request, jsonify
import functools
import base64
import functools
from core.error import ArcError, NoAccess
from core.user import UserAuth, UserLogin
from core.sql import Connect
from .func import error_return
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')

View File

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

File diff suppressed because it is too large Load Diff

View 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()

View File

@@ -59,8 +59,8 @@ def error_return(e: ArcError = default_error): # 错误返回
return jsonify(r)
def success_return(value):
return jsonify({
"success": True,
"value": value
})
def success_return(value=None):
r = {"success": True}
if value is not None:
r['value'] = value
return jsonify(r)

View File

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

View File

@@ -1,6 +1,6 @@
import os
from shutil import copy, copy2
from server.sql import Connect
from core.sql import Connect
from database.database_initialize import main, ARCAEA_SERVER_VERSION
from web.system import update_database
@@ -91,8 +91,4 @@ def check_before_run(app):
app.logger.warning(
'Fail to update the file `database/arcaea_database.db`.')
if not os.path.exists('database/arcsong.db'):
app.logger.warning('File `database/arcsong.db` is missing.')
f = False
return f

View File

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

View File

@@ -0,0 +1,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()

View 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()

View 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()

View 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()

View 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()

View File

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

View File

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

View File

@@ -1,13 +1,15 @@
from flask import Blueprint, request
from core.error import ArcError, NoAccess
from core.sql import Connect
from core.user import UserRegister, UserLogin, User, UserOnline
from core.character import UserCharacter
from core.error import ArcError, NoAccess
from core.item import ItemCore
from .func import error_return, success_return
from .auth import auth_required
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')
@@ -41,6 +43,17 @@ def register():
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):
@@ -67,13 +80,14 @@ def toggle_uncap(user_id, character_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]})
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']) # 角色觉醒
# 角色觉醒
@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:
@@ -82,13 +96,14 @@ def character_first_uncap(user_id, character_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})
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']) # 角色使用以太之滴
# 角色使用以太之滴
@bp.route('/me/character/<int:character_id>/exp', methods=['POST'])
@auth_required(request)
def character_exp(user_id, character_id):
with Connect() as c:
@@ -106,6 +121,72 @@ def character_exp(user_id, character_id):
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):

View 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()

View File

@@ -19,7 +19,7 @@ class Config():
游戏API地址前缀
Game API's URL prefix
'''
GAME_API_PREFIX = '/years/19'
GAME_API_PREFIX = '/divide/20'
'''
--------------------
'''
@@ -30,7 +30,7 @@ class Config():
Allowed game versions
If it is blank, all are allowed.
'''
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '3.12.10', '3.12.10c']
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '4.0.0', '4.0.0c']
'''
--------------------
'''
@@ -247,18 +247,10 @@ class Config():
--------------------
是否强制使用全解锁云端存档
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
'''
--------------------
'''
'''
--------------------
是否使用最好的 10 条记录(而不是最近的 30 条记录中较好的 10 条)来计算 PTT
Calculate PTT with best 10 instead of recent best 10
'''
USE_B10_AS_R10 = False
'''
--------------------
'''

View File

@@ -11,7 +11,7 @@
<span>Sid: </span>
<span class="song-title">{{song['song_id']}}</span>
<br />
<span>Name_en: </span>
<span>Name: </span>
<span class="song-title">{{song['name_en']}}</span>
<br />
<div>铺面定数 Chart const: </div>

View File

@@ -11,11 +11,11 @@
</form>
<div class="content">
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的全角色列表。<br />
可上传文件: arcaea_database.db和arcsong.db<br />
可上传文件: arcaea_database.db<br />
新数据库不存在的数据会被添加,存在的重复数据也会被改变。<br /><br />
Here you can synchronize the old version of the database to the new version of the database and refresh the list of full
characters owned by players.<br />
Uploadable files: arcaea_database.db & arcsong.db<br />
Uploadable files: arcaea_database.db<br />
Data that does not exist in the new database will be added and the existing duplicate data will also be changed.
</div>
<br />

View File

@@ -1,3 +1,6 @@
from .udp_config import Config
def b(value, length=1):
return value.to_bytes(length=length, byteorder='little')
@@ -35,7 +38,7 @@ class Player:
self.last_timestamp = 0
self.extra_command_queue = []
self.song_unlock = b'\x00' * 512
self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
self.start_command_num = 0
@@ -58,7 +61,7 @@ class Room:
self.song_idx = 0xffff
self.last_song_idx = 0xffff
self.song_unlock = b'\x00' * 512
self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
self.host_id = 0
self.players = [Player(), Player(), Player(), Player()]
@@ -120,12 +123,12 @@ class Room:
def update_song_unlock(self):
# 更新房间可用歌曲
r = bi(b'\xff' * 512)
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, 512)
self.song_unlock = b(r, Config.LINK_PLAY_UNLOCK_LENGTH)
def is_ready(self, old_state: int, player_state: int):
# 是否全部准备就绪

View File

@@ -5,4 +5,7 @@ class Config:
COUNTDOWM_TIME = 3999
PLAYER_TIMEOUT = 30000000
PLAYER_PRE_TIMEOUT = 3000000
PLAYER_TIMEOUT = 20000000
LINK_PLAY_UNLOCK_LENGTH = 512

View File

@@ -1,9 +1,9 @@
from operator import irshift
from .udp_sender import CommandSender
from .udp_class import bi, Room
from .udp_config import Config
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:
@@ -169,13 +169,14 @@ class CommandParser:
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 >= 5000000:
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))
@@ -183,10 +184,10 @@ class CommandParser:
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
flag_13 = False
if player.online == 0:
flag_12 = True

View File

@@ -1,16 +1,16 @@
from flask import (
Blueprint, flash, redirect, render_template, request, url_for
)
from web.login import login_required
from werkzeug.utils import secure_filename
from server.sql import Connect
import web.webscore
import web.system
import time
import server.arcscore
import os
import json
from server.arcdownload import initialize_songfile
import time
import server.arcscore
from core.download import initialize_songfile
from core.rank import RankList
from core.sql import Connect
from flask import Blueprint, flash, redirect, render_template, request, url_for
from werkzeug.utils import secure_filename
import web.system
import web.webscore
from web.login import login_required
UPLOAD_FOLDER = 'database'
ALLOWED_EXTENSIONS = {'db'}
@@ -184,21 +184,21 @@ def all_song():
return None
error = None
with Connect('./database/arcsong.db') as c:
c.execute('''select * from songs''')
with Connect() as c:
c.execute('''select * from chart''')
x = c.fetchall()
if x:
posts = []
for i in x:
posts.append({'song_id': i[0],
'name_en': i[1],
'rating_pst': defnum(i[13]),
'rating_prs': defnum(i[14]),
'rating_ftr': defnum(i[15]),
'rating_byn': defnum(i[16])
'rating_pst': defnum(i[2]),
'rating_prs': defnum(i[3]),
'rating_ftr': defnum(i[4]),
'rating_byn': defnum(i[5])
})
else:
error = '没有面数据 No song data.'
error = '没有面数据 No song data.'
if error:
flash(error)
@@ -216,25 +216,30 @@ def single_chart_top():
difficulty = request.form['difficulty']
if difficulty.isdigit():
difficulty = int(difficulty)
else:
difficulty = 0
error = None
x = None
with Connect('./database/arcsong.db') as c:
with Connect() as c:
song_name = '%'+song_name+'%'
c.execute('''select sid, name_en from songs where sid like :a limit 1''',
c.execute('''select song_id, name from chart where song_id like :a limit 1''',
{'a': song_name})
x = c.fetchone()
if x:
song_id = x[0]
posts = server.arcscore.arc_score_top(song_id, difficulty, -1)
for i in posts:
i['time_played'] = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(i['time_played']))
else:
error = '查询为空 No song.'
if x:
y = RankList(c)
y.song.set_chart(x[0], difficulty)
y.limit = -1
y.select_top()
posts = y.to_dict_list()
for i in posts:
i['time_played'] = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(i['time_played']))
else:
error = '查询为空 No song.'
if not error:
return render_template('web/singlecharttop.html', posts=posts, song_name_en=x[1], song_id=song_id, difficulty=difficulty)
return render_template('web/singlecharttop.html', posts=posts, song_name_en=x[1], song_id=x[0], difficulty=difficulty)
else:
flash(error)
@@ -260,7 +265,7 @@ def update_database():
flash('未选择文件 No selected file.')
return redirect(request.url)
if file and allowed_file(file.filename) and file.filename in ['arcsong.db', 'arcaea_database.db']:
if file and allowed_file(file.filename) and file.filename in ['arcaea_database.db']:
filename = 'old_' + secure_filename(file.filename)
file.save(os.path.join(UPLOAD_FOLDER, filename))
flash('上传成功 Success upload.')
@@ -282,11 +287,11 @@ def update_database():
@login_required
def update_song_hash():
# 更新数据库内谱面文件hash值
error = initialize_songfile()
if error:
flash(error)
else:
try:
initialize_songfile()
flash('数据刷新成功 Success refresh data.')
except:
flash('Something error!')
return render_template('web/updatedatabase.html')
@@ -336,11 +341,11 @@ def add_song():
if len(name_en) >= 256:
name_en = name_en[:200]
with Connect('./database/arcsong.db') as c:
with Connect() as c:
c.execute(
'''select exists(select * from songs where sid=:a)''', {'a': song_id})
'''select exists(select * from chart where song_id=:a)''', {'a': song_id})
if c.fetchone() == (0,):
c.execute('''insert into songs(sid,name_en,rating_pst,rating_prs,rating_ftr,rating_byn) values(:a,:b,:c,:d,:e,:f)''', {
c.execute('''insert into chart values(:a,:b,:c,:d,:e,:f)''', {
'a': song_id, 'b': name_en, 'c': rating_pst, 'd': rating_prs, 'e': rating_ftr, 'f': rating_byd})
flash('歌曲添加成功 Successfully add the song.')
else:
@@ -359,11 +364,11 @@ def delete_song():
error = None
song_id = request.form['sid']
with Connect('./database/arcsong.db') as c:
with Connect() as c:
c.execute(
'''select exists(select * from songs where sid=:a)''', {'a': song_id})
'''select exists(select * from chart where song_id=:a)''', {'a': song_id})
if c.fetchone() == (1,):
c.execute('''delete from songs where sid=:a''', {'a': song_id})
c.execute('''delete from chart where song_id=:a''', {'a': song_id})
flash('歌曲删除成功 Successfully delete the song.')
else:
error = "歌曲不存在 The song doesn't exist."
@@ -418,7 +423,7 @@ def all_character():
def change_character():
# 修改角色数据
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', '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', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita']
'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', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', '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', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis']
return render_template('web/changechar.html', skill_ids=skill_ids)

View File

@@ -1,8 +1,6 @@
import os
from server.sql import Connect
from core.sql import Connect
import time
import json
import server.arcscore
import hashlib
from random import Random
from setting import Config
@@ -22,7 +20,7 @@ def random_str(randomlength=10):
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
for _ in range(randomlength):
s += chars[random.randint(0, length)]
return s
@@ -179,7 +177,6 @@ def update_user_char(c):
def update_database():
# 将old数据库不存在数据加入到新数据库上并删除old数据库
# 对于arcaea_datebase.db更新一些表并用character数据更新user_char_full
# 对于arcsong.db更新songs
if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"):
with Connect('./database/old_arcaea_database.db') as c1:
with Connect() as c2:
@@ -206,6 +203,12 @@ def update_database():
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_one_table(c1, c2, 'user_char')
@@ -216,15 +219,6 @@ def update_database():
os.remove('database/old_arcaea_database.db')
# songs
if os.path.isfile("database/old_arcsong.db") and os.path.isfile("database/arcsong.db"):
with Connect('./database/old_arcsong.db') as c1:
with Connect('./database/arcsong.db') as c2:
update_one_table(c1, c2, 'songs')
os.remove('database/old_arcsong.db')
def unlock_all_user_item(c):
# 解锁所有用户购买
@@ -321,44 +315,45 @@ def get_all_purchase():
def update_one_save(c, user_id):
# 同步指定用户存档
# 注意best_score表不比较直接覆盖
c.execute('''select scores_data, clearlamps_data from user_save where user_id=:a''', {
'a': user_id})
x = c.fetchone()
if x:
scores = json.loads(x[0])[""]
clearlamps = json.loads(x[1])[""]
clear_song_id_difficulty = []
clear_state = []
for i in clearlamps:
clear_song_id_difficulty.append(i['song_id']+str(i['difficulty']))
clear_state.append(i['clear_type'])
for i in scores:
rating = server.arcscore.get_one_ptt(
i['song_id'], i['difficulty'], i['score'])
if rating < 0:
rating = 0
try:
index = clear_song_id_difficulty.index(
i['song_id'] + str(i['difficulty']))
except:
index = -1
if index != -1:
clear_type = clear_state[index]
else:
clear_type = 0
c.execute('''delete from best_score where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': i['song_id'], 'c': i['difficulty']})
c.execute('''insert into best_score values(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n)''', {
'a': user_id, 'b': i['song_id'], 'c': i['difficulty'], 'd': i['score'], 'e': i['shiny_perfect_count'], 'f': i['perfect_count'], 'g': i['near_count'], 'h': i['miss_count'], 'i': i['health'], 'j': i['modifier'], 'k': i['time_played'], 'l': clear_type, 'm': clear_type, 'n': rating})
ptt = server.arcscore.get_user_ptt(c, user_id) # 更新PTT
c.execute('''update user set rating_ptt=:a where user_id=:b''', {
'a': ptt, 'b': user_id})
return
# 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):
# 同步所有用户存档

60
tools/update_song.py Normal file
View File

@@ -0,0 +1,60 @@
import sqlite3
class Connect():
# 数据库连接类,上下文管理
def __init__(self, file_path='./arcaea_database.db'):
"""
数据库连接默认连接arcaea_database.db\
接受:文件路径\
返回sqlite3连接操作对象
"""
self.file_path = file_path
def __enter__(self):
self.conn = sqlite3.connect(self.file_path)
self.c = self.conn.cursor()
return self.c
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
if exc_type is not None:
if self.conn:
self.conn.rollback()
if self.conn:
self.conn.commit()
self.conn.close()
return True
def insert(cursor, song_id, name, a, b, c, d):
'''Insert a new song into database.'''
cursor.execute(
'''select exists(select * from chart where song_id=?)''', (song_id, ))
if cursor.fetchone()[0]:
return None
cursor.execute(
'''insert into chart values (?,?,?,?,?,?)''', (song_id, name, a, b, c, d))
def old_to_new():
'''Update old database to new database.'''
with Connect('./arcsong.db') as c:
c.execute(
'''select sid, name_en, rating_pst, rating_prs, rating_ftr, rating_byn from songs''')
data = c.fetchall()
with Connect() as c:
for x in data:
insert(c, x[0], x[1], x[2], x[3], x[4], x[5])
def main():
old_to_new()
if __name__ == '__main__':
main()
print('Done.')
input()
exit()