Hide keyboard shortcuts

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 

3 

4# -- stdlib -- 

5from typing import TYPE_CHECKING, cast 

6 

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 

17 

18# -- typing -- 

19if TYPE_CHECKING: 

20 from thb.thbkof import THBattleKOF # noqa: F401 

21 

22 

23# -- code -- 

24class MiracleHeal(Heal): 

25 pass 

26 

27 

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

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

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

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

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

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

156class SanaeFaithKOF(Skill): 

157 associated_action = None 

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

159 target = t_None() 

160 

161 

162class SanaeFaithKOFDrawCards(DrawCards): 

163 pass 

164 

165 

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

208class GodDescendant(Skill): 

209 associated_action = None 

210 target = t_None() 

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

212 

213 

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 

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 

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

242class 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') 

266class Sanae(Character): 

267 skills = [Miracle, SanaeFaith, GodDescendant] 

268 eventhandlers = [GodDescendantHandler] 

269 maxlife = 3 

270 

271 

272@register_character_to('kof') 

273class SanaeKOF(Character): 

274 skills = [Miracle, SanaeFaithKOF, GodDescendant] 

275 eventhandlers = [SanaeFaithKOFHandler, GodDescendantHandler] 

276 maxlife = 3