# Coverage for thb/characters/yuuka.py : 33%

## 137 statements 57 run 80 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

6

7# -- third party --

8# -- own --

9 from thb.actions import Damage, DrawCards, ForEach, LaunchCard, PlayerDeath, PlayerTurn, UserAction

10 from thb.actions import user_choose_players

11 from thb.cards.base import Skill

12 from thb.cards.classes import AttackCard, Duel, InstantSpellCardAction, Reject, TreatAs, t_None

13 from thb.characters.base import Character, register_character_to

14 from thb.inputlets import ChooseOptionInputlet

15 from thb.mode import THBEventHandler

16

17# -- typing --

18 if TYPE_CHECKING:

19 from thb.thbkof import THBattleKOF # noqa: F401

20

21

22# -- code --

23 class ReversedScales(TreatAs, Skill):

24 skill_category = ['character', 'active', 'compulsory']

25 treat_as = AttackCard

26

27 def check(self):

28 cl = self.associated_cards

29 if not cl or len(cl) != 1:

30 return False

31

32 g = self.game

33 if PlayerTurn.get_current(g).target is self.character:

34 return False

35

36 return cl[0].resides_in.type in ('cards', 'showncards')

37

38

39 class Sadist(Skill):

40 associated_action = None

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

42 target = t_None()

43

44

45 class SadistKOF(Skill):

46 associated_action = None

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

48 target = t_None()

49

50

51 class SadistKOFDrawCards(DrawCards):

52 pass

53

54

55 class SadistKOFDamageAction(UserAction):

56 def apply_action(self):

57 g = self.game

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

59 return g.process_action(Damage(src, tgt, 1))

60

61

62 class SadistKOFHandler(THBEventHandler):

63 interested = ['action_after', 'character_debut']

64 execute_after = ['DeathHandler']

65 game: THBattleKOF

66

67 def handle(self, evt_type, arg):

68 if evt_type == 'action_after' and isinstance(arg, PlayerDeath):

69 tgt = arg.target

70 g = self.game

71 op = g.get_opponent(tgt)

72 if arg.source is op and op.has_skill(SadistKOF):

73 g.process_action(SadistKOFDrawCards(op, 2))

74 op.tags['sadist_kof_fire'] = True

75

76 elif evt_type == 'character_debut':

77 old, new = arg

78 if not old: return arg

79

80 g = self.game

81 op = g.get_opponent(new)

82

83 if op.has_skill(SadistKOF) and op.tags['sadist_kof_fire']:

84 op.tags['sadist_kof_fire'] = False

85 g.process_action(SadistKOFDamageAction(op, new))

86

87 return arg

88

89

90 class ReversedScalesAction(UserAction):

91 def __init__(self, target, action):

92 self.source = target

93 self.target = target

94 self.action = action

95

96 def apply_action(self):

97 self.action.__class__ = Duel

98 return True

99

100

101 class ReversedScalesHandler(THBEventHandler):

102 interested = ['action_before']

103 execute_before = ['MaidenCostumeHandler']

104

105 def handle(self, evt_type, act):

106 if evt_type != 'action_before':

107 return act

108

109 if not isinstance(act, InstantSpellCardAction):

110 return act

111

112 if isinstance(act, Reject):

113 # HACK

114 return act

115

116 if ForEach.is_group_effect(act):

117 return act

118

119 src = act.source

120 tgt = act.target

121

122 if not src or tgt is src:

123 return act

124

125 if not tgt.has_skill(ReversedScales):

126 return act

127

128 g = self.game

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

130 return act

131

132 g.process_action(ReversedScalesAction(tgt, act))

133

134 return act

135

136

137 class SadistAction(UserAction):

138 def __init__(self, source, target):

139 self.source = source

140 self.target = target

141

142 def apply_action(self):

143 g = self.game

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

145 tgt.tags['sadist_target'] = False

146 g.process_action(Damage(src, tgt, 1))

147 return True

148

149

150 class SadistHandler(THBEventHandler):

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

152 card_usage = 'drop'

153 execute_before = ['WineHandler']

154 execute_after = ['DeathHandler']

155

156 def handle(self, evt_type, act):

157 if evt_type == 'action_after' and isinstance(act, PlayerDeath):

158 src = act.source

159 if not src or not src.has_skill(Sadist):

160 return act

161

162 g = self.game

163 dist = LaunchCard.calc_distance(g, src, AttackCard())

164 candidates = [k for k, v in dist.items() if v <= 0 and k is not src]

165

166 if not candidates:

167 return act

168

169 pl = user_choose_players(self, src, candidates)

170 if pl:

171 self.game.process_action(SadistAction(src, pl[0]))

172

173 elif evt_type == 'action_before' and isinstance(act, Damage):

174 src = act.source

175 tgt = act.target

176

177 if not src or src is tgt:

178 return act

179

180 if not src.has_skill(Sadist):

181 return act

182

183 if tgt.life == 1:

184 act.amount += 1

185

186 return act

187

188 def choose_player_target(self, pl):

189 return pl[-1:], len(pl)

190

191

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

193 class Yuuka(Character):

194 skills = [ReversedScales, Sadist]

195 eventhandlers = [ReversedScalesHandler, SadistHandler]

196 maxlife = 4

197

198

199@register_character_to('kof')

200 class YuukaKOF(Character):

201 skills = [ReversedScales, SadistKOF]

202 eventhandlers = [ReversedScalesHandler, SadistKOFHandler]

203 maxlife = 4