# Coverage for thb/characters/shizuha.py : 25%

## 150 statements 51 run 99 missing 0 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 cast

6

7# -- third party --

8# -- own --

9 from thb.actions import Damage, DrawCards, DropCardStage, DropCards, FinalizeStage, GenericAction

10 from thb.actions import MigrateCardsTransaction, PlayerTurn, UserAction, random_choose_card

11 from thb.actions import user_choose_players

12 from thb.cards.base import Skill, VirtualCard

13 from thb.cards.classes import t_None

14 from thb.characters.base import Character, register_character_to

15 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

16 from thb.mode import THBEventHandler

17

18

19# -- code --

20 class AutumnWindEffect(GenericAction):

21 def apply_action(self):

22 src, tgt = self.source, self.target

23

24 g = self.game

25

26 catnames = ('cards', 'showncards', 'equips')

27 cats = [getattr(tgt, i) for i in catnames]

28 card = g.user_input([src], ChoosePeerCardInputlet(self, tgt, catnames))

29 card = card or random_choose_card(g, cats)

30 if not card:

31 return False

32

33 self.card = card

34 g.players.exclude(tgt).reveal(card)

35 g.process_action(DropCards(src, tgt, cards=[card]))

36 return True

37

38 def is_valid(self):

39 tgt = self.target

40 catnames = ('cards', 'showncards', 'equips')

41 return not tgt.dead and any(getattr(tgt, i) for i in catnames)

42

43

44 class AutumnWindAction(UserAction):

45 def __init__(self, source, target_list):

46 self.source = source

47 self.target = source

48 self.target_list = target_list

49

50 def apply_action(self):

51 g = self.game

52 src = self.source

53

54 for p in self.target_list:

55 g.process_action(AutumnWindEffect(src, p))

56

57 return True

58

59

60 class AutumnWindHandler(THBEventHandler):

61 interested = ['action_after']

62

63 def handle(self, evt_type, act):

64 if evt_type == 'action_after' and isinstance(act, DropCardStage):

65 self.n = n = act.dropn

66 if n <= 0:

67 return act

68

69 tgt = act.target

70 if not tgt.has_skill(AutumnWind):

71 return act

72

73 g = self.game

74 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):

75 return act

76

77 candidates = [

78 p for p in g.players if

79 p is not tgt and

80 (p.cards or p.showncards or p.equips) and

81 not p.dead

82 ]

83

84 pl = candidates and user_choose_players(self, tgt, candidates)

85 if not pl:

86 return act

87

88 g.process_action(AutumnWindAction(tgt, pl))

89

90 return act

91

92 def choose_player_target(self, tl):

93 if not tl:

94 return (tl, False)

95

96 return (tl[:self.n], True)

97

98

99 class AutumnWind(Skill):

100 associated_action = None

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

102 target = t_None()

103

104

105 class DecayDrawCards(DrawCards):

106 pass

107

108

109 class DecayDrawCardHandler(THBEventHandler):

110 interested = ['post_card_migration']

111 execute_before = ['LuckHandler']

112

113 def handle(self, evt_type, arg):

114 if evt_type != 'post_card_migration':

115 return arg

116

117 g = self.game

118

119 try:

120 me = PlayerTurn.get_current(g).target

121 except IndexError:

122 return arg

123

124 if me is None: return arg

125 if me.dead: return arg

126 if not me.has_skill(Decay): return arg

127

128 trans = cast(MigrateCardsTransaction, arg)

129

130 candidates = {}

131

132 for m in trans.movements:

133 src = m.fr.owner

134 if not src: continue

135 if m.card.is_card(VirtualCard): continue

136 if m.fr.type not in ('cards', 'showncards'): continue

137 if src.cards or src.showncards: continue

138 if src.dead: continue

139 if src is me: continue

140 candidates[src] = 1

141

142 if not candidates:

143 return arg

144

145 g.process_action(DecayDrawCards(me, len(candidates)))

146

147 return arg

148

149

150 class DecayAction(UserAction):

151 def apply_action(self):

152 tgt = self.target

153 tgt.tags['shizuha_decay'] = True

154 return True

155

156

157 class DecayEffect(UserAction):

158 def __init__(self, source, target, dcs):

159 self.source = source

160 self.target = target

161 self.dcs = dcs

162

163 def apply_action(self):

164 self.dcs.dropn = max(1, self.dcs.dropn + 1)

165 return True

166

167

168 class DecayDamageHandler(THBEventHandler):

169 interested = ['action_after', 'action_before']

170 execute_after = [

171 'DyingHandler',

172 'AyaRoundfanHandler',

173 'NenshaPhoneHandler',

174 'SuwakoHatHandler',

175 ]

176

177 def handle(self, evt_type, act):

178 if evt_type == 'action_after' and isinstance(act, Damage):

179 src, tgt = act.source, act.target

180 if not (tgt and tgt.has_skill(Decay)):

181 return act

182

183 g = self.game

184

185 try:

186 cur = PlayerTurn.get_current(g).target

187 except IndexError:

188 # Yugi AssaultKOF & Yuuka Sadist

189 return act

190

191 if cur is tgt: return act

192 g.process_action(DecayAction(src, cur))

193

194 elif evt_type == 'action_before' and isinstance(act, DropCardStage):

195 tgt = act.target

196 t = tgt.tags

197 if not t['shizuha_decay']: return act

198

199 t['shizuha_decay'] = False

200 g = self.game

201 g.process_action(DecayEffect(tgt, tgt, act))

202

203 return act

204

205

206 class DecayFadeHandler(THBEventHandler):

207 interested = ['action_after']

208

209 def handle(self, evt_type, act):

210 if evt_type == 'action_after' and isinstance(act, FinalizeStage):

211 tgt = act.target

212 if tgt.tags['shizuha_decay']:

213 tgt.tags['shizuha_decay'] = False

214

215 return act

216

217

218 class Decay(Skill):

219 associated_action = None

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

221 target = t_None()

222

223

224@register_character_to('common')

225 class Shizuha(Character):

226 skills = [Decay, AutumnWind]

227 eventhandlers = [DecayDamageHandler, DecayDrawCardHandler, DecayFadeHandler, AutumnWindHandler]

228 maxlife = 3