# Coverage for thb/characters/sanae.py : 35%

## 183 statements 78 run 105 missing 2 excluded 0 partial

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 -*-

2 from __future__ import annotations

3

4# -- stdlib --

5 from typing import TYPE_CHECKING, cast

6

7# -- third party --

8# -- own --

9 from thb.actions import ActionStage, AskForCard, DistributeCards, DrawCardStage, DrawCards

10 from thb.actions import GenericAction, MigrateCardsTransaction, PlayerTurn, Reforge, UserAction

11 from thb.actions import migrate_cards, random_choose_card, ttags, user_choose_cards

12 from thb.actions import user_choose_players

13 from thb.cards.base import Skill

14 from thb.cards.classes import Heal, PhysicalCard, t_None, t_Self

15 from thb.characters.base import Character, register_character_to

16 from thb.mode import THBEventHandler

17

18# -- typing --

19 if TYPE_CHECKING:

20 from thb.thbkof import THBattleKOF # noqa: F401

21

22

23# -- code --

24 class MiracleHeal(Heal):

25 pass

26

27

28 class MiracleAction(UserAction):

29 def apply_action(self):

30 tgt = self.target

31 g = self.game

32 g.process_action(DrawCards(tgt, 1))

33

34 ttags(tgt)['miracle_times'] += 1

35

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

42

43 return True

44

45 def choose_player_target(self, tl):

46 return tl[-1:], bool(tl)

47

48 def cond(self, cl):

49 return True

50

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

56

57

58 class Miracle(Skill):

59 associated_action = MiracleAction

60 skill_category = ['character', 'active']

61 target = t_Self()

62 usage = 'drop'

63

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 )

72

73

74 class SanaeFaithCollectCardAction(GenericAction):

75 card_usage = 'handover'

76 no_reveal = True

77

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)

85

86 return True

87

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)

92

93

94 class SanaeFaithReturnCardAction(GenericAction):

95 card_usage = 'handover'

96 no_reveal = True

97

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

105

106 tgt.player.reveal(c)

107 migrate_cards([c], tgt.cards)

108

109 return True

110

111 def cond(self, cl):

112 return len(cl) == 1 and not cl[0].is_card(Skill)

113

114

115 class SanaeFaithAction(UserAction):

116 def apply_action(self):

117 src = self.source

118 tl = self.target_list

119 g = self.game

120

121 for p in tl:

122 g.process_action(SanaeFaithCollectCardAction(src, p))

123

124 g.deck.shuffle(src.cards)

125

126 for p in tl:

127 g.process_action(SanaeFaithReturnCardAction(src, p))

128

129 ttags(src)['faith'] = True

130

131 return True

132

133 def is_valid(self):

134 src = self.source

135 return not ttags(src)['faith']

136

137

138 class SanaeFaith(Skill):

139 associated_action = SanaeFaithAction

140 skill_category = ['character', 'active']

141 usage = 'launch'

142

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

149

150 return (tl[:2], bool(len(tl)))

151

152 def check(self):

153 return not self.associated_cards

154

155

156 class SanaeFaithKOF(Skill):

157 associated_action = None

158 skill_category = ['character', 'passive']

159 target = t_None()

160

161

162 class SanaeFaithKOFDrawCards(DrawCards):

163 pass

164

165

166 class SanaeFaithKOFHandler(THBEventHandler):

167 game: THBattleKOF

168 interested = ['post_card_migration']

169

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

175

176 g = self.game

177

178 for m in trans.movements:

179 if not m.to.owner:

180 continue

181

182 if m.to.type not in ('cards', 'showncards', 'equips'):

183 continue

184

185 if m.fr.owner is m.to.owner:

186 continue

187

188 tgt = g.get_opponent(m.to.owner)

189

190 if not tgt.has_skill(SanaeFaithKOF):

191 continue

192

193 turn = PlayerTurn.get_current(g)

194 if not turn:

195 continue

196

197 stage = turn.current_stage

198 if stage.target is not m.to.owner or not isinstance(stage, ActionStage):

199 continue

200

201 g.process_action(SanaeFaithKOFDrawCards(tgt, 1))

202

203 break

204

205 return arg

206

207

208 class GodDescendant(Skill):

209 associated_action = None

210 target = t_None()

211 skill_category = ['character', 'passive']

212

213

214 class 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

220

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

228

229

230 class GodDescendantAction(AskForCard):

231 card_usage = 'reforge'

232

233 def __init__(self, target, target_list):

234 AskForCard.__init__(self, target, target, PhysicalCard, ('cards', 'showncards', 'equips'))

235 self.target_list = target_list

236

237 def process_card(self, c):

238 g = self.game

239 return g.process_action(GodDescendantEffect(self.source, self.target, self.target_list, c))

240

241

242 class GodDescendantHandler(THBEventHandler):

243 interested = ['choose_target']

244 execute_before = ['MaidenCostumeHandler']

245

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

251

252 g = self.game

253 for tgt in tl:

254 if not tgt.has_skill(GodDescendant):

255 continue

256

257 g.process_action(GodDescendantAction(tgt, tl))

258

259 return arg

260

261 def cond(self, cl):

262 return len(cl) == 1

263

264

265@register_character_to('common', '-kof')

266 class Sanae(Character):

267 skills = [Miracle, SanaeFaith, GodDescendant]

268 eventhandlers = [GodDescendantHandler]

269 maxlife = 3

270

271

272@register_character_to('kof')

273 class SanaeKOF(Character):

274 skills = [Miracle, SanaeFaithKOF, GodDescendant]

275 eventhandlers = [SanaeFaithKOFHandler, GodDescendantHandler]

276 maxlife = 3