Skip to content

Commit 8bf10f8

Browse files
committed
Implement "First clear" loveca bonus.
Fixes #36.
1 parent 166aef1 commit 8bf10f8

File tree

4 files changed

+123
-8
lines changed

4 files changed

+123
-8
lines changed

npps4/game/live.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
import itertools
23
import json
34
import math
45

@@ -13,6 +14,7 @@
1314
from ..system import class_system as class_system_module
1415
from ..system import common
1516
from ..system import effort
17+
from ..system import item
1618
from ..system import live
1719
from ..system import live_model
1820
from ..system import museum
@@ -588,6 +590,30 @@ async def live_reward(context: idol.SchoolIdolUserParams, request: LiveRewardReq
588590
live_clear_data.hi_combo_cnt = max(live_clear_data.hi_combo_cnt, request.max_combo)
589591
live_clear_data.clear_cnt = live_clear_data.clear_cnt + 1
590592

593+
special_reward: list[common.AnyItem] = []
594+
if live_clear_data.clear_cnt == 1:
595+
# First clear. Give loveca only if:
596+
# * User cleared Easy, Normal, and Hard
597+
# * User cleared Expert
598+
# * User cleared Master
599+
if live_setting.difficulty > 3:
600+
# User cleared Expert or higher. Give loveca directly.
601+
special_reward.append(item.loveca(1))
602+
else:
603+
# User cleared Easy, Normal, or Hard. Only give loveca if all is cleared.
604+
enh_list = (await live.get_enh_live_difficulty_ids(context, request.live_difficulty_id)).copy()
605+
enh_list[live_setting.difficulty] = request.live_difficulty_id
606+
cleared = True
607+
608+
for i in range(1, 4):
609+
live_clear_data_adjacent = await live.get_live_clear_data(context, current_user, enh_list[i])
610+
if live_clear_data_adjacent is None or live_clear_data_adjacent.clear_cnt == 0:
611+
cleared = False
612+
break
613+
614+
if cleared:
615+
special_reward.append(item.loveca(1))
616+
591617
# Get accomplished live goals
592618
old_live_goals = set(await live.get_achieved_goal_id_list(context, old_live_clear_data))
593619
new_live_goals = set(await live.get_achieved_goal_id_list(context, live_clear_data))
@@ -598,7 +624,7 @@ async def live_reward(context: idol.SchoolIdolUserParams, request: LiveRewardReq
598624
)
599625

600626
# Give live goal rewards
601-
for reward_data in live_goal_rewards:
627+
for reward_data in itertools.chain(live_goal_rewards, special_reward):
602628
await advanced.add_item(context, current_user, reward_data)
603629

604630
# This is the intended EXP and G drop
@@ -815,7 +841,7 @@ async def live_reward(context: idol.SchoolIdolUserParams, request: LiveRewardReq
815841
goal_accomp_info=LiveRewardGoalAccomplishedInfo(
816842
achieved_ids=accomplished_live_goals, rewards=live_goal_rewards
817843
),
818-
special_reward_info=[], # TODO: Give 1 loveca on clearing this track for the first time.
844+
special_reward_info=special_reward,
819845
accomplished_achievement_list=await achievement.to_game_representation(
820846
context, accomplished_achievement.accomplished, accomplished_achievement_rewards
821847
),

npps4/game/profile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def profile_livecount(context: idol.SchoolIdolUserParams, request: Profile
6969
if target_user is None:
7070
raise idol.error.by_code(idol.error.ERROR_CODE_USER_NOT_EXIST)
7171

72-
cleared = await live.get_cleard_live_count(context, target_user)
72+
cleared = await live.get_cleared_live_count(context, target_user)
7373
return ProfileLiveCountResponse(
7474
[ProfileLiveCount(difficulty=i, clear_cnt=cleared.get(i, 0)) for i in (1, 2, 3, 4, 6)]
7575
)

npps4/system/live.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ async def init(context: idol.BasicSchoolIdolContext, user: main.User):
4444
for normallive in result.scalars():
4545
setting_data = await context.db.live.get(live.LiveSetting, normallive.live_setting_id)
4646
assert setting_data is not None
47-
# live_clear = main.LiveClear(
48-
# user_id=user.id, live_difficulty_id=normallive.live_difficulty_id, difficulty=setting_data.difficulty
49-
# )
50-
# context.db.main.add(live_clear)
5147
if setting_data.live_track_id not in unlocked:
5248
if await unlock_normal_live(context, user, setting_data.live_track_id):
5349
unlocked.add(setting_data.live_track_id)
@@ -618,9 +614,41 @@ async def pull_precise_score_with_beatmap(
618614
return json.loads(gzip.decompress(replay.precise_log)), notes_list, replay.timestamp
619615

620616

621-
async def get_cleard_live_count(context: idol.BasicSchoolIdolContext, /, user: main.User) -> dict[int, int]:
617+
async def get_cleared_live_count(context: idol.BasicSchoolIdolContext, /, user: main.User) -> dict[int, int]:
622618
q = sqlalchemy.select(main.LiveClear.difficulty, sqlalchemy.func.count(main.LiveClear.live_difficulty_id)).group_by(
623619
main.LiveClear.difficulty
624620
)
625621
result = await context.db.main.execute(q)
626622
return {r[0]: r[1] for r in result}
623+
624+
625+
@common.context_cacheable("adjacent_live_difficulty_id")
626+
async def get_enh_live_difficulty_ids(context: idol.BasicSchoolIdolContext, /, live_difficulty_id: int):
627+
output: dict[int, int] = {}
628+
629+
live_info_base = await get_live_info_table(context, live_difficulty_id)
630+
if live_info_base is None:
631+
raise ValueError(f"invalid live_difficulty_id {live_difficulty_id}")
632+
633+
live_setting = await get_live_setting(context, live_info_base.live_setting_id)
634+
if live_setting is None:
635+
raise ValueError(f"invalid live_setting_id {live_info_base.live_setting_id}")
636+
637+
q = sqlalchemy.select(live.LiveSetting).where(
638+
live.LiveSetting.live_track_id == live_setting.live_track_id, live.LiveSetting.difficulty <= 3
639+
)
640+
live_settings = (await context.db.live.execute(q)).scalars().all()
641+
live_setting_map = {l.live_setting_id: l for l in live_settings}
642+
643+
live_info_type = type(live_info_base)
644+
q = sqlalchemy.select(live_info_type).where(
645+
live_info_type.live_setting_id.in_([l.live_setting_id for l in live_settings])
646+
)
647+
live_infos = (await context.db.live.execute(q)).scalars().all()
648+
649+
for live_info in live_infos:
650+
output[live_setting_map[live_info.live_setting_id].difficulty] = live_info.live_difficulty_id
651+
# Also set cache for the retrieved live difficulty ids
652+
context.set_cache("adjacent_live_difficulty_id", live_info.live_difficulty_id, output)
653+
654+
return output
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import npps4.script_dummy # isort:skip
2+
3+
import sqlalchemy
4+
5+
import npps4.idol
6+
import npps4.db.main
7+
import npps4.system.item
8+
import npps4.system.live
9+
import npps4.system.reward
10+
11+
revision = "7_give_loveca_to_cleared_songs"
12+
prev_revision = "6_send_subunit_rewards_take2"
13+
14+
FIXES_ACHIEVEMENT_IDS = [10090010, *range(10090012, 10090019)]
15+
16+
17+
async def main(context: npps4.idol.BasicSchoolIdolContext):
18+
q = sqlalchemy.select(npps4.db.main.User)
19+
20+
async for target_user in await context.db.main.stream_scalars(q):
21+
live_difficulty_id_checked: set[int] = set()
22+
loveca = 0
23+
24+
q = sqlalchemy.select(npps4.db.main.LiveClear).where(npps4.db.main.LiveClear.user_id == target_user.id)
25+
async for live_clear in await context.db.main.stream_scalars(q):
26+
if live_clear.live_difficulty_id in live_difficulty_id_checked:
27+
continue
28+
29+
live_setting = await npps4.system.live.get_live_setting_from_difficulty_id(
30+
context, live_clear.live_difficulty_id
31+
)
32+
if live_setting is None:
33+
raise ValueError(f"invalid live_difficulty_id {live_clear.live_difficulty_id}")
34+
35+
if live_setting.difficulty > 3:
36+
# User cleared Expert or higher. Give loveca directly.
37+
loveca = loveca + 1
38+
else:
39+
# User cleared Easy, Normal, or Hard. Only give loveca if all is cleared.
40+
enh_list = (
41+
await npps4.system.live.get_enh_live_difficulty_ids(context, live_clear.live_difficulty_id)
42+
).copy()
43+
enh_list[live_setting.difficulty] = live_clear.live_difficulty_id
44+
cleared = True
45+
46+
for i in range(1, 4):
47+
live_clear_data_adjacent = await npps4.system.live.get_live_clear_data(
48+
context, target_user, enh_list[i]
49+
)
50+
if live_clear_data_adjacent is None or live_clear_data_adjacent.clear_cnt == 0:
51+
cleared = False
52+
break
53+
54+
if cleared:
55+
loveca = loveca + 1
56+
live_difficulty_id_checked.update(enh_list.values())
57+
58+
if loveca > 0:
59+
print(f"Given {loveca} loveca to user {target_user.id} ({target_user.invite_code})")
60+
item = npps4.system.item.loveca(loveca)
61+
await npps4.system.reward.add_item(context, target_user, item, "First Live Clear Bonuses")

0 commit comments

Comments
 (0)