Coverage for thb/characters/sanae.py : 96%
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 -*-
3# -- stdlib --
4# -- third party --
5# -- own --
6from typing import cast
7from thb.actions import ActionStage, AskForCard, DistributeCards, DrawCardStage, DrawCards, MigrateCardsTransaction
8from thb.actions import GenericAction, PlayerTurn, Reforge, UserAction, migrate_cards
9from thb.actions import random_choose_card, ttags, user_choose_cards, user_choose_players
10from thb.cards.base import Skill
11from thb.cards.classes import Heal, PhysicalCard, t_None, t_Self
12from thb.characters.base import Character, register_character_to
13from thb.mode import THBEventHandler
16# -- code --
17class MiracleHeal(Heal):
18 pass
21class MiracleAction(UserAction):
22 def apply_action(self):
23 tgt = self.target
24 g = self.game
25 g.process_action(DrawCards(tgt, 1))
27 ttags(tgt)['miracle_times'] += 1
29 if ttags(tgt)['miracle_times'] == 3:
30 candidates = [p for p in g.players if not p.dead and p.life < p.maxlife]
31 if candidates:
32 beneficiery, = user_choose_players(self, tgt, candidates) or (None,)
33 if beneficiery: 33 ↛ 36line 33 didn't jump to line 36, because the condition on line 33 was never false
34 g.process_action(MiracleHeal(tgt, beneficiery))
36 return True
38 def choose_player_target(self, tl):
39 return tl[-1:], bool(tl)
41 def cond(self, cl):
42 return True
44 def is_valid(self):
45 tgt = self.target
46 return len(self.associated_card.associated_cards) == ttags(tgt)['miracle_times'] + 1
49class Miracle(Skill):
50 associated_action = MiracleAction
51 skill_category = ['character', 'active']
52 target = t_Self()
53 usage = 'drop'
55 def check(self):
56 cl = self.associated_cards
57 return cl and all(
58 c.resides_in is not None and
59 c.resides_in.type in (
60 'cards', 'showncards', 'equips'
61 ) for c in self.associated_cards
62 )
65class SanaeFaithCollectCardAction(GenericAction):
66 card_usage = 'handover'
67 no_reveal = True
69 def apply_action(self) -> bool:
70 g = self.game
71 src, tgt = self.source, self.target
72 cards = user_choose_cards(self, tgt, ('cards', 'showncards'))
73 c = cards[0] if cards else random_choose_card(g, [tgt.cards, tgt.showncards])
74 src.player.reveal(c)
75 migrate_cards([c], src.cards)
77 return True
79 def cond(self, cl):
80 return len(cl) == 1 and \
81 cl[0].resides_in.type in ('cards', 'showncards') and \
82 not cl[0].is_card(Skill)
85class SanaeFaithReturnCardAction(GenericAction):
86 card_usage = 'handover'
87 no_reveal = True
89 def apply_action(self) -> bool:
90 g = self.game
91 src, tgt = self.source, self.target
92 cards = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))
93 c = cards[0] if cards else random_choose_card(g, [src.cards, src.showncards, src.equips])
94 if not c: 94 ↛ 95line 94 didn't jump to line 95, because the condition on line 94 was never true
95 return False
97 tgt.player.reveal(c)
98 migrate_cards([c], tgt.cards)
100 return True
102 def cond(self, cl):
103 return len(cl) == 1 and not cl[0].is_card(Skill)
106class SanaeFaithAction(UserAction):
107 def apply_action(self):
108 src = self.source
109 tl = self.target_list
110 g = self.game
112 for p in tl:
113 g.process_action(SanaeFaithCollectCardAction(src, p))
115 g.deck.shuffle(src.cards)
117 for p in tl:
118 g.process_action(SanaeFaithReturnCardAction(src, p))
120 ttags(src)['faith'] = True
122 return True
124 def is_valid(self):
125 src = self.source
126 return not ttags(src)['faith']
129class SanaeFaith(Skill):
130 associated_action = SanaeFaithAction
131 skill_category = ['character', 'active']
132 usage = 'launch'
134 def target(self, src, tl):
135 tl = [t for t in tl if not t.dead and (t.cards or t.showncards)]
136 try:
137 tl.remove(src)
138 except ValueError:
139 pass
141 return (tl[:2], bool(len(tl)))
143 def check(self):
144 return not self.associated_cards
147class SanaeFaithKOF(Skill):
148 associated_action = None
149 skill_category = ['character', 'passive']
150 target = t_None()
153class SanaeFaithKOFDrawCards(DrawCards):
154 pass
157class SanaeFaithKOFHandler(THBEventHandler):
158 interested = ['post_card_migration']
160 def handle(self, evt_type, arg):
161 if evt_type == 'post_card_migration': 161 ↛ 195line 161 didn't jump to line 195, because the condition on line 161 was never false
162 trans = cast(MigrateCardsTransaction, arg)
163 if isinstance(trans.action, (DistributeCards, DrawCardStage)):
164 return arg
166 g = self.game
168 for m in trans.movements:
169 if not m.to.owner:
170 continue
172 if m.to.type not in ('cards', 'showncards', 'equips'):
173 continue
175 if m.fr.owner is m.to.owner:
176 continue
178 tgt = g.get_opponent(m.to.owner)
180 if not tgt.has_skill(SanaeFaithKOF):
181 continue
183 turn = PlayerTurn.get_current(g)
184 if not turn: 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true
185 continue
187 stage = turn.current_stage
188 if stage.target is not m.to.owner or not isinstance(stage, ActionStage):
189 continue
191 g.process_action(SanaeFaithKOFDrawCards(tgt, 1))
193 break
195 return arg
198class GodDescendant(Skill):
199 associated_action = None
200 target = t_None()
201 skill_category = ['character', 'passive']
204class GodDescendantEffect(UserAction):
205 def __init__(self, source, target, target_list, card):
206 self.source = source
207 self.target = target
208 self.target_list = target_list
209 self.card = card
211 def apply_action(self):
212 g = self.game
213 src, tgt, tl = self.source, self.target, self.target_list
214 g.process_action(Reforge(src, tgt, self.card))
215 assert tgt in tl
216 tl.remove(tgt)
217 return True
220class GodDescendantAction(AskForCard):
221 card_usage = 'reforge'
223 def __init__(self, target, target_list):
224 AskForCard.__init__(self, target, target, PhysicalCard, ('cards', 'showncards', 'equips'))
225 self.target_list = target_list
227 def process_card(self, c):
228 g = self.game
229 return g.process_action(GodDescendantEffect(self.source, self.target, self.target_list, c))
232class GodDescendantHandler(THBEventHandler):
233 interested = ['choose_target']
234 execute_before = ['MaidenCostumeHandler']
236 def handle(self, evt_type, arg):
237 if evt_type == 'choose_target': 237 ↛ 249line 237 didn't jump to line 249, because the condition on line 237 was never false
238 act, tl = arg
239 if 'group_effect' not in act.card.category:
240 return arg
242 g = self.game
243 for tgt in tl:
244 if not tgt.has_skill(GodDescendant):
245 continue
247 g.process_action(GodDescendantAction(tgt, tl))
249 return arg
251 def cond(self, cl):
252 return len(cl) == 1
255@register_character_to('common', '-kof')
256class Sanae(Character):
257 skills = [Miracle, SanaeFaith, GodDescendant]
258 eventhandlers = [GodDescendantHandler]
259 maxlife = 3
262@register_character_to('kof')
263class SanaeKOF(Character):
264 skills = [Miracle, SanaeFaithKOF, GodDescendant]
265 eventhandlers = [SanaeFaithKOFHandler, GodDescendantHandler]
266 maxlife = 3