mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-13 03:27:26 +08:00
[Enhance] Missions & ETR
- Add support for missions - PTT mechanism: Change first play protection to new best protection - Adapt to the new difficulty ETR - Uncap DORO*C - Incomplete support for "pick_ticket" - Fix requirements: cryptography >= 35.0.0 Note: This is an intermediate test version, only for Arcaea 5.4.0c. Next version will adapt to 5.4.0.
This commit is contained in:
@@ -12,7 +12,7 @@ class Config:
|
||||
|
||||
SONG_FILE_HASH_PRE_CALCULATE = True
|
||||
|
||||
GAME_API_PREFIX = '/samusugiru/26' # str | list[str]
|
||||
GAME_API_PREFIX = '/saikyoukaze/27' # str | list[str]
|
||||
OLD_GAME_API_PREFIX = [] # str | list[str]
|
||||
|
||||
ALLOW_APPVERSION = [] # list[str]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .config_manager import Config
|
||||
|
||||
ARCAEA_SERVER_VERSION = 'v2.11.3.4'
|
||||
ARCAEA_SERVER_VERSION = 'v2.11.3.5'
|
||||
ARCAEA_LOG_DATBASE_VERSION = 'v1.1'
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ class DatabaseInit:
|
||||
('memory', 'memory', 1))
|
||||
self.c.execute('''insert into item values(?,?,?)''',
|
||||
('anni5tix', 'anni5tix', 1))
|
||||
self.c.execute('''insert into item values(?,?,?)''',
|
||||
('pick_ticket', 'pick_ticket', 1))
|
||||
|
||||
with open(self.pack_path, 'rb') as f:
|
||||
self.insert_purchase_item(load(f))
|
||||
@@ -99,6 +101,9 @@ class DatabaseInit:
|
||||
with open(self.single_path, 'rb') as f:
|
||||
self.insert_purchase_item(load(f))
|
||||
|
||||
self.c.execute('''insert into item values(?,?,?)''', # 新手任务奖励曲
|
||||
('innocence', 'world_song', 1))
|
||||
|
||||
def course_init(self) -> None:
|
||||
'''初始化课题信息'''
|
||||
courses = []
|
||||
|
||||
@@ -89,7 +89,7 @@ class UserItem(Item):
|
||||
|
||||
|
||||
class NormalItem(UserItem):
|
||||
def __init__(self, c) -> None:
|
||||
def __init__(self, c=None) -> None:
|
||||
super().__init__()
|
||||
self.c = c
|
||||
|
||||
@@ -115,7 +115,7 @@ class NormalItem(UserItem):
|
||||
|
||||
|
||||
class PositiveItem(UserItem):
|
||||
def __init__(self, c) -> None:
|
||||
def __init__(self, c=None) -> None:
|
||||
super().__init__()
|
||||
self.c = c
|
||||
|
||||
@@ -142,7 +142,7 @@ class PositiveItem(UserItem):
|
||||
class ItemCore(PositiveItem):
|
||||
item_type = 'core'
|
||||
|
||||
def __init__(self, c, core_type: str = '', amount: int = 0) -> None:
|
||||
def __init__(self, c=None, core_type: str = '', amount: int = 0) -> None:
|
||||
super().__init__(c)
|
||||
self.is_available = True
|
||||
self.item_id = core_type
|
||||
@@ -220,10 +220,12 @@ class Memory(UserItem):
|
||||
class Fragment(UserItem):
|
||||
item_type = 'fragment'
|
||||
|
||||
def __init__(self, c) -> None:
|
||||
def __init__(self, c=None, amount=0) -> None:
|
||||
super().__init__()
|
||||
self.c = c
|
||||
self.is_available = True
|
||||
self.item_id = self.item_type
|
||||
self.amount = amount
|
||||
|
||||
def user_claim_item(self, user):
|
||||
pass
|
||||
@@ -238,12 +240,24 @@ class Anni5tix(PositiveItem):
|
||||
def __init__(self, c) -> None:
|
||||
super().__init__(c)
|
||||
self.is_available = True
|
||||
self.item_id = self.item_type
|
||||
self.amount = 1
|
||||
|
||||
|
||||
class PickTicket(PositiveItem):
|
||||
item_type = 'pick_ticket'
|
||||
|
||||
def __init__(self, c=None) -> None:
|
||||
super().__init__(c)
|
||||
self.is_available = True
|
||||
self.item_id = self.item_type
|
||||
self.amount = 1
|
||||
|
||||
|
||||
class WorldSong(NormalItem):
|
||||
item_type = 'world_song'
|
||||
|
||||
def __init__(self, c) -> None:
|
||||
def __init__(self, c=None) -> None:
|
||||
super().__init__(c)
|
||||
self.is_available = True
|
||||
|
||||
@@ -293,8 +307,10 @@ class ProgBoost(UserItem):
|
||||
class Stamina6(UserItem):
|
||||
item_type = 'stamina6'
|
||||
|
||||
def __init__(self, c) -> None:
|
||||
def __init__(self, c=None) -> None:
|
||||
super().__init__(c)
|
||||
self.item_id = 'stamina6'
|
||||
self.amount = 1
|
||||
|
||||
def user_claim_item(self, user):
|
||||
'''
|
||||
@@ -307,6 +323,23 @@ class Stamina6(UserItem):
|
||||
user.update_user_one_column('world_mode_locked_end_ts', -1)
|
||||
|
||||
|
||||
class ItemStamina(UserItem):
|
||||
item_type = 'stamina'
|
||||
|
||||
def __init__(self, c=None, amount=1) -> None:
|
||||
super().__init__(c)
|
||||
self.item_id = 'stamina'
|
||||
self.amount = amount
|
||||
|
||||
def user_claim_item(self, user):
|
||||
'''
|
||||
新手任务奖励体力
|
||||
'''
|
||||
user.select_user_about_stamina()
|
||||
user.stamina.stamina += self.amount
|
||||
user.stamina.update()
|
||||
|
||||
|
||||
class ItemFactory:
|
||||
def __init__(self, c=None) -> None:
|
||||
self.c = c
|
||||
@@ -324,6 +357,8 @@ class ItemFactory:
|
||||
return Memory(self.c)
|
||||
elif item_type == 'anni5tix':
|
||||
return Anni5tix(self.c)
|
||||
elif item_type == 'pick_ticket':
|
||||
return PickTicket(self.c)
|
||||
elif item_type == 'world_song':
|
||||
return WorldSong(self.c)
|
||||
elif item_type == 'world_unlock':
|
||||
|
||||
240
latest version/core/mission.py
Normal file
240
latest version/core/mission.py
Normal file
@@ -0,0 +1,240 @@
|
||||
from .item import Fragment, ItemCore, ItemStamina, PickTicket, WorldSong
|
||||
|
||||
|
||||
class Mission:
|
||||
mission_id: str = None
|
||||
items: list = []
|
||||
|
||||
def __init__(self, c=None):
|
||||
self.c = c
|
||||
self.user = None
|
||||
self._status: int = None
|
||||
|
||||
if self.c is not None:
|
||||
for i in self.items:
|
||||
i.c = self.c
|
||||
|
||||
def to_dict(self, has_items=False) -> dict:
|
||||
r = {
|
||||
'mission_id': self.mission_id,
|
||||
'status': self.status,
|
||||
}
|
||||
if has_items:
|
||||
r['items'] = [x.to_dict() for x in self.items]
|
||||
return r
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
if self._status == 1:
|
||||
return 'inprogress'
|
||||
elif self._status == 2:
|
||||
return 'cleared'
|
||||
elif self._status == 3:
|
||||
return 'prevclaimedfragmission'
|
||||
elif self._status == 4:
|
||||
return 'claimed'
|
||||
|
||||
return 'locked'
|
||||
|
||||
def user_claim_mission(self, user):
|
||||
# param: user - User 类或子类的实例
|
||||
if user is not None:
|
||||
self.user = user
|
||||
|
||||
self.c.execute('''insert or replace into user_mission (user_id, mission_id, status) values (?, ?, 4)''',
|
||||
(self.user.user_id, self.mission_id))
|
||||
for i in self.items:
|
||||
i.user_claim_item(self.user)
|
||||
self._status = 4
|
||||
|
||||
def user_clear_mission(self, user):
|
||||
# param: user - User 类或子类的实例
|
||||
if user is not None:
|
||||
self.user = user
|
||||
|
||||
self.c.execute('''insert or replace into user_mission (user_id, mission_id, status) values (?, ?, 2)''',
|
||||
(self.user.user_id, self.mission_id))
|
||||
self._status = 2
|
||||
|
||||
def select_user_mission(self, user):
|
||||
# param: user - User 类或子类的实例
|
||||
if user is not None:
|
||||
self.user = user
|
||||
|
||||
self._status = 0
|
||||
self.c.execute('''select status from user_mission where user_id=? and mission_id=?''',
|
||||
(self.user.user_id, self.mission_id))
|
||||
x = self.c.fetchone()
|
||||
|
||||
if x and x[0]:
|
||||
self._status = x[0]
|
||||
|
||||
|
||||
class M11(Mission):
|
||||
mission_id = 'mission_1_1_tutorial'
|
||||
items = [Fragment(amount=10)]
|
||||
|
||||
|
||||
class M12(Mission):
|
||||
mission_id = 'mission_1_2_clearsong'
|
||||
items = [Fragment(amount=10)]
|
||||
|
||||
|
||||
class M13(Mission):
|
||||
mission_id = 'mission_1_3_settings'
|
||||
items = [Fragment(amount=10)]
|
||||
|
||||
|
||||
class M14(Mission):
|
||||
mission_id = 'mission_1_4_allsongsview'
|
||||
items = [Fragment(amount=10)]
|
||||
|
||||
|
||||
class M15(Mission):
|
||||
mission_id = 'mission_1_5_fragunlock'
|
||||
items = [ItemCore(core_type='core_generic', amount=1)]
|
||||
|
||||
|
||||
class M1E(Mission):
|
||||
mission_id = 'mission_1_end'
|
||||
items = [Fragment(amount=100)]
|
||||
|
||||
|
||||
class M21(Mission):
|
||||
mission_id = 'mission_2_1_account'
|
||||
items = [Fragment(amount=20)]
|
||||
|
||||
|
||||
class M22(Mission):
|
||||
mission_id = 'mission_2_2_profile'
|
||||
items = [Fragment(amount=20)]
|
||||
|
||||
|
||||
class M23(Mission):
|
||||
mission_id = 'mission_2_3_partner'
|
||||
items = [Fragment(amount=20)]
|
||||
|
||||
|
||||
class M24(Mission):
|
||||
mission_id = 'mission_2_4_usestamina'
|
||||
items = [ItemCore(core_type='core_generic', amount=1)]
|
||||
|
||||
|
||||
class M25(Mission):
|
||||
mission_id = 'mission_2_5_prologuestart'
|
||||
items = [ItemCore(core_type='core_generic', amount=1)]
|
||||
|
||||
|
||||
class M2E(Mission):
|
||||
mission_id = 'mission_2_end'
|
||||
items = [ItemCore(core_type='core_generic', amount=3)]
|
||||
|
||||
|
||||
class M31(Mission):
|
||||
mission_id = 'mission_3_1_prsclear'
|
||||
items = [Fragment(amount=50)]
|
||||
|
||||
|
||||
class M32(Mission):
|
||||
mission_id = 'mission_3_2_etherdrop'
|
||||
items = [ItemStamina(amount=2)]
|
||||
|
||||
|
||||
class M33(Mission):
|
||||
mission_id = 'mission_3_3_step50'
|
||||
items = [Fragment(amount=50)]
|
||||
|
||||
|
||||
class M34(Mission):
|
||||
mission_id = 'mission_3_4_frag60'
|
||||
items = [ItemStamina(amount=2)]
|
||||
|
||||
|
||||
class M3E(Mission):
|
||||
mission_id = 'mission_3_end'
|
||||
items = [ItemStamina(amount=6)]
|
||||
|
||||
|
||||
class M41(Mission):
|
||||
mission_id = 'mission_4_1_exgrade'
|
||||
items = [Fragment(amount=100)]
|
||||
|
||||
|
||||
class M42(Mission):
|
||||
mission_id = 'mission_4_2_potential350'
|
||||
items = [ItemStamina(amount=2)]
|
||||
|
||||
|
||||
class M43(Mission):
|
||||
mission_id = 'mission_4_3_twomaps'
|
||||
items = [Fragment(amount=100)]
|
||||
|
||||
|
||||
class M44(Mission):
|
||||
mission_id = 'mission_4_4_worldsongunlock'
|
||||
items = [ItemCore(core_type='core_generic', amount=3)]
|
||||
|
||||
|
||||
class M45(Mission):
|
||||
mission_id = 'mission_4_5_prologuefinish'
|
||||
items = [ItemStamina(amount=2)]
|
||||
|
||||
|
||||
_innocence = WorldSong()
|
||||
_innocence.amount = 1
|
||||
_innocence.item_id = 'innocence'
|
||||
|
||||
|
||||
class M4E(Mission):
|
||||
mission_id = 'mission_4_end'
|
||||
items = [_innocence]
|
||||
|
||||
|
||||
class M51(Mission):
|
||||
mission_id = 'mission_5_1_songgrouping'
|
||||
items = [Fragment(amount=50)]
|
||||
|
||||
|
||||
class M52(Mission):
|
||||
mission_id = 'mission_5_2_partnerlv12'
|
||||
items = [Fragment(amount=250)]
|
||||
|
||||
|
||||
class M53(Mission):
|
||||
mission_id = 'mission_5_3_cores'
|
||||
items = [ItemCore(core_type='core_generic', amount=3)]
|
||||
|
||||
|
||||
class M54(Mission):
|
||||
mission_id = 'mission_5_4_courseclear'
|
||||
items = [ItemCore(core_type='core_generic', amount=3)]
|
||||
|
||||
|
||||
class M5E(Mission):
|
||||
mission_id = 'mission_5_end'
|
||||
items = [PickTicket()]
|
||||
|
||||
|
||||
MISSION_DICT = {i.mission_id: i for i in Mission.__subclasses__()}
|
||||
|
||||
|
||||
class UserMissionList:
|
||||
def __init__(self, c=None, user=None):
|
||||
self.c = c
|
||||
self.user = user
|
||||
|
||||
self.missions: list = []
|
||||
|
||||
def select_all(self):
|
||||
self.missions = []
|
||||
self.c.execute('''select mission_id, status from user_mission where user_id=?''',
|
||||
(self.user.user_id,))
|
||||
for i in self.c.fetchall():
|
||||
x = MISSION_DICT[i[0]]()
|
||||
x._status = i[1]
|
||||
self.missions.append(x)
|
||||
|
||||
return self
|
||||
|
||||
def to_dict_list(self) -> list:
|
||||
return [i.to_dict() for i in self.missions]
|
||||
@@ -29,7 +29,7 @@ class Purchase(CollectionItemMixin):
|
||||
|
||||
self.items: list = []
|
||||
|
||||
# TODO: "discount_reason": "extend"
|
||||
# TODO: "discount_reason": extend, sale
|
||||
|
||||
@property
|
||||
def price_displayed(self) -> int:
|
||||
@@ -44,6 +44,12 @@ class Purchase(CollectionItemMixin):
|
||||
x.select_user_item(self.user)
|
||||
if x.amount >= 1:
|
||||
return 0
|
||||
elif self.discount_reason == 'pick_ticket':
|
||||
x = ItemFactory(self.c).get_item('pick_ticket')
|
||||
x.item_id = 'pick_ticket'
|
||||
x.select_user_item(self.user)
|
||||
if x.amount >= 1:
|
||||
return 0
|
||||
return self.price
|
||||
return self.orig_price
|
||||
|
||||
@@ -60,7 +66,7 @@ class Purchase(CollectionItemMixin):
|
||||
if self.discount_from > 0 and self.discount_to > 0:
|
||||
r['discount_from'] = self.discount_from
|
||||
r['discount_to'] = self.discount_to
|
||||
if not show_real_price or (self.discount_reason == 'anni5tix' and price == 0):
|
||||
if not show_real_price or (self.discount_reason in ('anni5tix', 'pick_ticket') and price == 0):
|
||||
r['discount_reason'] = self.discount_reason
|
||||
return r
|
||||
|
||||
@@ -186,10 +192,10 @@ class Purchase(CollectionItemMixin):
|
||||
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 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 = ItemFactory(self.c).get_item(self.discount_reason)
|
||||
x.item_id = self.discount_reason
|
||||
x.amount = -1
|
||||
x.user_claim_item(self.user)
|
||||
else:
|
||||
|
||||
@@ -207,7 +207,7 @@ class UserPlay(UserScore):
|
||||
self.submission_hash: str = None
|
||||
self.beyond_gauge: int = None
|
||||
self.unrank_flag: bool = None
|
||||
self.first_protect_flag: bool = None
|
||||
self.new_best_protect_flag: bool = None
|
||||
self.ptt: 'Potential' = None
|
||||
|
||||
self.is_world_mode: bool = None
|
||||
@@ -245,7 +245,7 @@ class UserPlay(UserScore):
|
||||
|
||||
@property
|
||||
def is_protected(self) -> bool:
|
||||
return self.health == -1 or int(self.score) >= 9800000 or self.first_protect_flag
|
||||
return self.health == -1 or int(self.score) >= 9800000 or self.new_best_protect_flag
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
@@ -473,16 +473,17 @@ class UserPlay(UserScore):
|
||||
'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.new_best_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
|
||||
self.new_best_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.new_best_protect_flag = True
|
||||
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()
|
||||
|
||||
@@ -33,7 +33,7 @@ class Chart:
|
||||
|
||||
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})
|
||||
'''select rating_pst, rating_prs, rating_ftr, rating_byn, rating_etr from chart where song_id=:a''', {'a': self.song_id})
|
||||
x = self.c.fetchone()
|
||||
if x is None:
|
||||
if Config.ALLOW_SCORE_WITH_NO_SONG:
|
||||
@@ -63,11 +63,12 @@ class Song:
|
||||
self.song_id = x[0]
|
||||
self.name = x[1]
|
||||
self.charts = [Chart(self.c, self.song_id, 0), Chart(self.c, self.song_id, 1), Chart(
|
||||
self.c, self.song_id, 2), Chart(self.c, self.song_id, 3)]
|
||||
self.c, self.song_id, 2), Chart(self.c, self.song_id, 3), Chart(self.c, self.song_id, 4)]
|
||||
self.charts[0].defnum = x[2]
|
||||
self.charts[1].defnum = x[3]
|
||||
self.charts[2].defnum = x[4]
|
||||
self.charts[3].defnum = x[5]
|
||||
self.charts[4].defnum = x[6]
|
||||
return self
|
||||
|
||||
def from_dict(self, d: dict) -> 'Song':
|
||||
@@ -89,11 +90,11 @@ class Song:
|
||||
def update(self) -> None:
|
||||
'''全部更新'''
|
||||
self.c.execute(
|
||||
'''update chart set name=?, rating_pst=?, rating_prs=?, rating_ftr=?, rating_byn=? where song_id=?''', (self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum, self.song_id))
|
||||
'''update chart set name=?, rating_pst=?, rating_prs=?, rating_ftr=?, rating_byn=?, rating_etr=? where song_id=?''', (self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum, self.charts[4].defnum, self.song_id))
|
||||
|
||||
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))
|
||||
'''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, self.charts[4].defnum))
|
||||
|
||||
def select_exists(self, song_id: str = None) -> bool:
|
||||
if song_id is not None:
|
||||
|
||||
@@ -11,6 +11,7 @@ from .error import (ArcError, DataExist, FriendError, InputError, NoAccess,
|
||||
NoData, RateLimit, UserBan)
|
||||
from .item import UserItemList
|
||||
from .limiter import ArcLimiter
|
||||
from .mission import UserMissionList
|
||||
from .score import Score
|
||||
from .sql import Query, Sql
|
||||
from .world import Map, UserMap, UserStamina
|
||||
@@ -349,6 +350,13 @@ class UserInfo(User):
|
||||
|
||||
return self.__packs
|
||||
|
||||
@property
|
||||
def pick_ticket(self) -> int:
|
||||
x = UserItemList(self.c, self).select_from_type('pick_ticket')
|
||||
if not x.items:
|
||||
return 0
|
||||
return x.items[0].amount
|
||||
|
||||
@property
|
||||
def world_unlocks(self) -> list:
|
||||
if self.__world_unlocks is None:
|
||||
@@ -520,7 +528,9 @@ class UserInfo(User):
|
||||
'country': '',
|
||||
'course_banners': self.course_banners,
|
||||
'world_mode_locked_end_ts': self.world_mode_locked_end_ts,
|
||||
'locked_char_ids': [] # [1]
|
||||
'locked_char_ids': [], # [1]
|
||||
'user_missions': UserMissionList(self.c, self).select_all().to_dict_list(),
|
||||
'pick_ticket': self.pick_ticket
|
||||
}
|
||||
|
||||
def from_list(self, x: list) -> 'UserInfo':
|
||||
|
||||
Reference in New Issue
Block a user