Coverage for thb/characters/sanae.py : 35%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2from __future__ import annotations
4# -- stdlib --
5from typing import TYPE_CHECKING, cast
7# -- third party --
8# -- own --
9from thb.actions import ActionStage, AskForCard, DistributeCards, DrawCardStage, DrawCards
10from thb.actions import GenericAction, MigrateCardsTransaction, PlayerTurn, Reforge, UserAction
11from thb.actions import migrate_cards, random_choose_card, ttags, user_choose_cards
12from thb.actions import user_choose_players
13from thb.cards.base import Skill
14from thb.cards.classes import Heal, PhysicalCard, t_None, t_Self
15from thb.characters.base import Character, register_character_to
16from thb.mode import THBEventHandler
18# -- typing --
19if TYPE_CHECKING:
20 from thb.thbkof import THBattleKOF # noqa: F401
23# -- code --
24class MiracleHeal(Heal):
25 pass
28class MiracleAction(UserAction):
29 def apply_action(self):
30 tgt = self.target
31 g = self.game
32 g.process_action(DrawCards(tgt, 1))
34 ttags(tgt)['miracle_times'] += 1
36 if ttags(tgt)['miracle_times'] == 3:
37 candidates = [p for p in g.players if not p.dead and p.life < p.maxlife]
38 if candidates:
39 beneficiery, = user_choose_players(self, tgt, candidates) or (None,)
40 if beneficiery:
41 g.process_action(MiracleHeal(tgt, beneficiery))
43 return True
45 def choose_player_target(self, tl):
46 return tl[-1:], bool(tl)
48 def cond(self, cl):
49 return True
51 def is_valid(self):
52 tgt = self.target
53 sk = self.associated_card
54 assert isinstance(sk, Miracle)
55 return len(sk.associated_cards) == ttags(tgt)['miracle_times'] + 1
58class Miracle(Skill):
59 associated_action = MiracleAction
60 skill_category = ['character', 'active']
61 target = t_Self()
62 usage = 'drop'
64 def check(self):
65 cl = self.associated_cards
66 return cl and all(
67 c.resides_in is not None and
68 c.resides_in.type in (
69 'cards', 'showncards', 'equips'
70 ) for c in self.associated_cards
71 )
74class SanaeFaithCollectCardAction(GenericAction):
75 card_usage = 'handover'
76 no_reveal = True
78 def apply_action(self) -> bool:
79 g = self.game
80 src, tgt = self.source, self.target
81 cards = user_choose_cards(self, tgt, ('cards', 'showncards'))
82 c = cards[0] if cards else random_choose_card(g, [tgt.cards, tgt.showncards])
83 src.player.reveal(c)
84 migrate_cards([c], src.cards)
86 return True
88 def cond(self, cl):
89 return len(cl) == 1 and \
90 cl[0].resides_in.type in ('cards', 'showncards') and \
91 not cl[0].is_card(Skill)
94class SanaeFaithReturnCardAction(GenericAction):
95 card_usage = 'handover'
96 no_reveal = True
98 def apply_action(self) -> bool:
99 g = self.game
100 src, tgt = self.source, self.target
101 cards = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))
102 c = cards[0] if cards else random_choose_card(g, [src.cards, src.showncards, src.equips])
103 if not c:
104 return False
106 tgt.player.reveal(c)
107 migrate_cards([c], tgt.cards)
109 return True
111 def cond(self, cl):
112 return len(cl) == 1 and not cl[0].is_card(Skill)
115class SanaeFaithAction(UserAction):
116 def apply_action(self):
117 src = self.source
118 tl = self.target_list
119 g = self.game
121 for p in tl:
122 g.process_action(SanaeFaithCollectCardAction(src, p))
124 g.deck.shuffle(src.cards)
126 for p in tl:
127 g.process_action(SanaeFaithReturnCardAction(src, p))
129 ttags(src)['faith'] = True
131 return True
133 def is_valid(self):
134 src = self.source
135 return not ttags(src)['faith']
138class SanaeFaith(Skill):
139 associated_action = SanaeFaithAction
140 skill_category = ['character', 'active']
141 usage = 'launch'
143 def target(self, src, tl):
144 tl = [t for t in tl if not t.dead and (t.cards or t.showncards)]
145 try:
146 tl.remove(src)
147 except ValueError:
148 pass
150 return (tl[:2], bool(len(tl)))
152 def check(self):
153 return not self.associated_cards
156class SanaeFaithKOF(Skill):
157 associated_action = None
158 skill_category = ['character', 'passive']
159 target = t_None()
162class SanaeFaithKOFDrawCards(DrawCards):
163 pass
166class SanaeFaithKOFHandler(THBEventHandler):
167 game: THBattleKOF
168 interested = ['post_card_migration']
170 def handle(self, evt_type, arg):
171 if evt_type == 'post_card_migration':
172 trans = cast(MigrateCardsTransaction, arg)
173 if isinstance(trans.action, (DistributeCards, DrawCardStage)):
174 return arg
176 g = self.game
178 for m in trans.movements:
179 if not m.to.owner:
180 continue
182 if m.to.type not in ('cards', 'showncards', 'equips'):
183 continue
185 if m.fr.owner is m.to.owner:
186 continue
188 tgt = g.get_opponent(m.to.owner)
190 if not tgt.has_skill(SanaeFaithKOF):
191 continue
193 turn = PlayerTurn.get_current(g)
194 if not turn:
195 continue
197 stage = turn.current_stage
198 if stage.target is not m.to.owner or not isinstance(stage, ActionStage):
199 continue
201 g.process_action(SanaeFaithKOFDrawCards(tgt, 1))
203 break
205 return arg
208class GodDescendant(Skill):
209 associated_action = None
210 target = t_None()
211 skill_category = ['character', 'passive']
214class GodDescendantEffect(UserAction):
215 def __init__(self, source, target, target_list, card):
216 self.source = source
217 self.target = target
218 self.target_list = target_list
219 self.card = card
221 def apply_action(self):
222 g = self.game
223 src, tgt, tl = self.source, self.target, self.target_list
224 g.process_action(Reforge(src, tgt, self.card))
225 assert tgt in tl
226 tl.remove(tgt)
227 return True
230class GodDescendantAction(AskForCard):
231 card_usage = 'reforge'
233 def __init__(self, target, target_list):
234 AskForCard.__init__(self, target, target, PhysicalCard, ('cards', 'showncards', 'equips'))
235 self.target_list = target_list
237 def process_card(self, c):
238 g = self.game
239 return g.process_action(GodDescendantEffect(self.source, self.target, self.target_list, c))
242class GodDescendantHandler(THBEventHandler):
243 interested = ['choose_target']
244 execute_before = ['MaidenCostumeHandler']
246 def handle(self, evt_type, arg):
247 if evt_type == 'choose_target':
248 act, tl = arg
249 if 'group_effect' not in act.card.category:
250 return arg
252 g = self.game
253 for tgt in tl:
254 if not tgt.has_skill(GodDescendant):
255 continue
257 g.process_action(GodDescendantAction(tgt, tl))
259 return arg
261 def cond(self, cl):
262 return len(cl) == 1
265@register_character_to('common', '-kof')
266class Sanae(Character):
267 skills = [Miracle, SanaeFaith, GodDescendant]
268 eventhandlers = [GodDescendantHandler]
269 maxlife = 3
272@register_character_to('kof')
273class SanaeKOF(Character):
274 skills = [Miracle, SanaeFaithKOF, GodDescendant]
275 eventhandlers = [SanaeFaithKOFHandler, GodDescendantHandler]
276 maxlife = 3