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

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