Code refactoring

- Code refactoring
- Fix a bug that the other player will not become the host of the room at once, when the player disconnect in link play.

> Maybe add many unknown bugs. XD
> The song database `arcsong.db` will not used in the future. You can use a tool in `tool` folder to import old data.
This commit is contained in:
Lost-MSth
2022-07-04 18:36:30 +08:00
parent 9de62d3645
commit 6fcca17918
49 changed files with 3663 additions and 3660 deletions

View File

@@ -2,7 +2,7 @@ import hashlib
import base64
import time
import random
from server.sql import Connect
from core.sql import Connect
import functools
from setting import Config
from flask import jsonify

View File

@@ -1,14 +1,13 @@
from server.sql import Connect
from server.sql import Sql
import time
from core.sql import Connect
from core.sql import Sql
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})
with Connect('') as c:
c.execute('''select * from chart where song_id=:a''', {'a': song_id})
x = c.fetchone()
if x:
r = {'song_id': x[0],
@@ -34,8 +33,8 @@ def get_songs(query=None):
# 查询全部歌曲信息,返回字典列表
r = []
with Connect('./database/arcsong.db') as c:
x = Sql.select(c, 'songs', [], query)
with Connect('') as c:
x = Sql.select(c, 'chart', [], query)
if x:
for i in x:

View File

@@ -2,12 +2,12 @@ from flask import (
Blueprint, request
)
from .api_code import code_get_msg, return_encode
from .api_code import 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
from core.sql import Connect
from core.sql import Sql
import time
import web.webscore
import server.info

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
@@ -114,18 +116,42 @@ class Character:
self.uncap_cores = []
self.voice = None
@property
def skill_id_displayed(self) -> str:
return None
@property
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
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 +163,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 +178,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:
@@ -184,6 +212,8 @@ class UserCharacter(Character):
@property
def to_dict(self):
if self.char_type is None:
self.select_character_info(self.user)
r = {"is_uncapped_override": self.is_uncapped_override,
"is_uncapped": self.is_uncapped,
"uncap_cores": self.uncap_cores_to_dict,
@@ -205,27 +235,31 @@ class UserCharacter(Character):
r['voice'] = self.voice
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 +269,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 +278,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 +317,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 +336,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,18 @@ class Constant:
ETO_UNCAP_BONUS_PROGRESS = 7
LUNA_UNCAP_BONUS_PROGRESS = 7
AYU_UNCAP_BONUS_PROGRESS = 5
MAX_FRIEND_COUNT = 50
BEST30_WEIGHT = 1 / 40
RECENT10_WEIGHT = 1 / 40
WORLD_MAP_FOLDER_PATH = './database/map/'
SONG_FILE_FOLDER_PATH = './database/songs/'
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
LINK_PLAY_TIMEOUT = 10

View File

@@ -0,0 +1,179 @@
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']:
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}
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=-999, 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,50 @@ 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`类或子类的实例
'''
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 +76,34 @@ 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):
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):
@@ -83,7 +117,7 @@ class ItemCore(PositiveItem):
self.amount = - core.amount if reverse else core.amount
class ItemCharacter(Item):
class ItemCharacter(UserItem):
item_type = 'character'
def __init__(self, c) -> None:
@@ -91,7 +125,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 +139,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 +149,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 +168,18 @@ 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
class Anni5tix(PositiveItem):
item_type = 'anni5tix'
@@ -144,6 +193,7 @@ class WorldSong(NormalItem):
def __init__(self, c) -> None:
super().__init__(c)
self.is_available = True
class WorldUnlock(NormalItem):
@@ -151,6 +201,7 @@ class WorldUnlock(NormalItem):
def __init__(self, c) -> None:
super().__init__(c)
self.is_available = True
class Single(NormalItem):
@@ -167,19 +218,125 @@ 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_prog_boost(1)
class Stamina6(UserItem):
item_type = 'stamina6'
def __init__(self, c) -> None:
super().__init__(c)
def user_claim_item(self, user):
'''
世界模式记忆源点买体力
'''
user.select_user_about_stamina()
user.stamina.stamina += 6
user.stamina.update()
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)
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
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] * 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)
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_about_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_about_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(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,160 @@
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
@property
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_about_ticket()
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_about_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 = []
@property
def to_dict(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

158
latest version/core/rank.py Normal file
View File

@@ -0,0 +1,158 @@
from .user import UserInfo
from .song import Chart
from .score import UserScore
from .constant import Constant
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
@property
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)

112
latest version/core/save.py Normal file
View File

@@ -0,0 +1,112 @@
from .util import md5
from .error import InputError
from setting import Config
from time import time
import json
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 = ''
self.devicemodelname_data = ''
self.story_data = []
self.createdAt = 0
@property
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
}
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])
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
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)''', {
'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})
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':
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,104 @@
from time import time
from .error import NoData, StaminaNotEnough
from .song import Chart
from .util import md5
from .constant import Constant
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,7 +116,10 @@ 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
@@ -71,3 +138,316 @@ 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.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])
@property
def to_dict(self) -> dict:
r = super().to_dict
r['user_id'] = self.user.user_id
r['name'] = self.user.name
r['best_clear_type'] = self.best_clear_type
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
@property
def to_dict(self) -> dict:
if self.is_world_mode is None:
return {}
elif not self.is_world_mode:
return {'global_rank': self.user.global_rank, 'user_rating': self.user.rating_ptt}
else:
r = self.world_play.to_dict
r['user_rating'] = self.user.rating_ptt
r['global_rank'] = self.user.global_rank
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检查服务器没有谱面就不管了
# TODO: 这里肯定是要改的了
self.c.execute('''select md5 from songfile where song_id=:a and file_type=:b''', {
'a': self.song.song_id, 'b': self.song.difficulty})
x = self.c.fetchone()
if x:
if x[0] != 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 stamina_multiply,fragment_multiply,prog_boost_multiply from world_songplay 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.is_world_mode = False
return None
self.stamina_multiply = int(x[0])
self.fragment_multiply = int(x[1])
self.prog_boost_multiply = int(x[2])
self.is_world_mode = True
def set_play_state(self, stamina_multiply: int = 1, fragment_multiply: int = 100, prog_boost_multiply: int = 0) -> None:
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.c.execute('''delete from world_songplay 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})
self.c.execute('''insert into world_songplay values(:a,:b,:c,: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})
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.stamina.stamina -= self.user.current_map.stamina_cost * self.stamina_multiply
self.user.stamina.update()
def clear_play_state(self) -> None:
self.c.execute('''delete from world_songplay 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})
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()
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
@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_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)

View File

@@ -1,7 +1,42 @@
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
@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:
raise NoData('The song `%s` does not exist.' % self.song_id)
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 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))

View File

@@ -8,9 +8,9 @@ class Connect():
def __init__(self, file_path='./database/arcaea_database.db'):
"""
数据库连接默认连接arcaea_database.db
接受:文件路径
返回sqlite3连接操作对象
数据库连接默认连接arcaea_database.db\
接受:文件路径\
返回sqlite3连接操作对象
"""
self.file_path = file_path
@@ -19,7 +19,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()

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,7 +39,7 @@ 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
@@ -269,7 +273,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
@@ -282,16 +286,77 @@ class UserOnline(User):
self.max_stamina_notification_enabled = False
self.prog_boost = 0
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
@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
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:
# 得到用户的朋友列表
@@ -350,16 +415,65 @@ class UserOnline(User):
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):
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
}
def select_user(self) -> None:
# 查user表所有信息
self.c.execute(
'''select * from user where user_id = :x''', {'x': self.user_id})
@@ -386,19 +500,205 @@ 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])
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 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)
def select_user_about_world_rank_score(self) -> None:
'''
查询user表有关世界模式排名的信息
'''
self.c.execute(
'''select world_rank_score from user where user_id=?''', (self.user_id,))
x = self.c.fetchone()
if not x:
raise NoData('No user.', 108, -3)
self.world_rank_score = x[0]
@property
def global_rank(self) -> int:
'''用户世界排名如果超过设定最大值返回0'''
if self.world_rank_score is None:
self.select_user_about_world_rank_score()
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_about_ticket(self) -> None:
'''
查询user表有关记忆源点的信息
'''
self.c.execute('''select ticket from user where user_id = :a''', {
'a': self.user_id})
x = self.c.fetchone()
if not x:
raise NoData('No user.', 108, -3)
self.ticket = x[0]
def update_user_about_ticket(self, ticket: int = None) -> None:
'''更新记忆源点'''
if ticket is not None:
self.ticket = ticket
self.c.execute('''update user set ticket = :a where user_id = :b''', {
'a': self.ticket, 'b': self.user_id})
def select_user_about_fragstam(self) -> None:
'''
查询user表有关碎片购买体力时间的信息
'''
self.c.execute('''select next_fragstam_ts from user where user_id = :a''', {
'a': self.user_id})
x = self.c.fetchone()
if not x:
raise NoData('No user.', 108, -3)
self.next_fragstam_ts = x[0] if x[0] else 0
def update_user_about_fragstam(self, next_fragstam_ts: int = None) -> None:
'''更新碎片购买体力时间'''
if next_fragstam_ts is not None:
self.next_fragstam_ts = next_fragstam_ts
self.c.execute('''update user set next_fragstam_ts = :a where user_id = :b''', {
'a': self.next_fragstam_ts, 'b': self.user_id})
def select_user_about_name(self) -> None:
'''
查询user表有关用户名的信息
'''
self.c.execute('''select name 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]
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 +711,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 +719,30 @@ class UserOnline(User):
{'x': self.user_id, 'y': friend_id})
else:
raise FriendError('No user or the user is not your friend.', 401)
def update_prog_boost(self, prog_boost: int = None) -> None:
'''更新`prog_boost`'''
if prog_boost:
self.prog_boost = prog_boost
self.c.execute('''update user set prog_boost = :a where user_id = :b''',
{'a': self.prog_boost, 'b': self.user_id})
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})
def change_is_hide_rating(self, is_hide_rating: bool = None) -> None:
'''更改用户的is_hide_rating'''
if is_hide_rating is not None:
self.is_hide_rating = is_hide_rating
self.c.execute('''update user set is_hide_rating = :a where user_id = :b''',
{'a': self.is_hide_rating, 'b': self.user_id})
def change_max_stamina_notification_enabled(self, max_stamina_notification_enabled: bool = None) -> None:
'''更改用户的max_stamina_notification_enabled'''
if max_stamina_notification_enabled is not None:
self.max_stamina_notification_enabled = max_stamina_notification_enabled
self.c.execute('''update user set max_stamina_notification_enabled = :a where user_id = :b''',
{'a': self.max_stamina_notification_enabled, '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,663 @@
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
@property
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
@property
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 = int(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 if stamina > 0 else 0
@stamina.setter
def stamina(self, value: int) -> None:
'''设置体力值此处会导致max_stamina_ts变化'''
self.__stamina = int(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
@property
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
}
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_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}
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)

Binary file not shown.

View File

@@ -4,7 +4,7 @@ import json
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
ARCAEA_SERVER_VERSION = 'v2.8.6'
ARCAEA_SERVER_VERSION = 'v2.8.7.dev'
def main(path='./'):
@@ -256,10 +256,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

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.api_main
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
@@ -38,100 +31,6 @@ app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.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,20 @@ 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
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)

View File

@@ -1,88 +1,3 @@
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):
@@ -104,207 +19,5 @@ def buy_item(c, user_id, 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,9 +1,4 @@
from server.config import Constant
from server.sql import Connect
import time
import json
import server.arcworld
import hashlib
from core.sql import Connect
from setting import Config
@@ -23,16 +18,6 @@ def int2b(x):
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''',
@@ -88,24 +73,6 @@ def get_score(c, user_id, song_id, difficulty):
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 = []
@@ -128,77 +95,6 @@ def arc_score_top(song_id, difficulty, limit=20):
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
def calculate_rating(defnum, score):
# 计算rating
if score >= 10000000:
@@ -213,349 +109,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 +148,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,14 +1,7 @@
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):
@@ -36,16 +29,6 @@ def calc_stamina(max_stamina_ts, curr_stamina):
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 = {}
@@ -55,74 +38,6 @@ def get_world_info(map_id):
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 = []
@@ -139,534 +54,3 @@ def get_available_maps():
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,9 +1,5 @@
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):
@@ -14,11 +10,6 @@ def int2b(x):
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):
# 计算搭档数值的核心函数,返回浮点数
@@ -59,22 +50,6 @@ def get_char_core(c, character_id):
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):
# 得到用户拥有的角色列表,返回列表
@@ -125,42 +100,6 @@ def get_user_character(c, user_id):
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):
# 计算角色升级,返回当前经验和等级
@@ -190,65 +129,3 @@ def calc_level_up(c, user_id, character_id, exp, exp_addition):
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

@@ -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,12 +1,8 @@
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):
@@ -197,92 +193,3 @@ def get_user_me(c, user_id):
}
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

@@ -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,92 @@
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
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()
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}
@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())
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,140 @@
from time import time
from core.error import ArcError, ItemUnavailable
from core.item import ItemFactory
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)
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)
except ArcError as e:
return error_return(e)
return error_return()
@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
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_about_fragstam()
now = int(time()*1000)
if user.next_fragstam_ts > now:
return ItemUnavailable('Buying stamina by fragment is not available yet.', 905)
user.update_user_about_fragstam(now + 24 * 3600 * 1000)
user.select_user_about_stamina()
user.stamina.stamina += 6
user.stamina.update()
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
})
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,114 @@
from time import time
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(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": "13145201919810"
}
)
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,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):
@@ -73,7 +86,8 @@ def toggle_uncap(user_id, character_id):
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:
@@ -88,7 +102,8 @@ def character_first_uncap(user_id, character_id):
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.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:
user.change_is_hide_rating(value)
elif 'max_stamina_notification_enabled' == set_arg:
user.change_max_stamina_notification_enabled(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

@@ -218,7 +218,7 @@ class Config():
是否全解锁搭档
If unlocking all partners is enabled
'''
CHARACTER_FULL_UNLOCK = True
CHARACTER_FULL_UNLOCK = 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

@@ -5,4 +5,4 @@ class Config:
COUNTDOWM_TIME = 3999
PLAYER_TIMEOUT = 30000000
PLAYER_TIMEOUT = 20000000

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 >= 3000000:
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,15 @@
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.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 +183,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)
@@ -218,9 +217,9 @@ def single_chart_top():
difficulty = int(difficulty)
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()
@@ -260,7 +259,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 +281,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 +335,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 +358,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."

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,7 @@ 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_char')
@@ -216,15 +214,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 +310,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()