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

## 127 statements 43 run 84 missing 0 excluded 1 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# -- third party --

6# -- own --

7 from thb.actions import ActionStageLaunchCard, Damage, DrawCardStage, FinalizeStage, GenericAction

8 from thb.actions import PlayerDeath, PlayerTurn, register_eh, ttags

9 from thb.cards.base import Skill

10 from thb.cards.classes import ActionLimitExceeded, AttackCard, AttackCardVitalityHandler, BaseAttack

11 from thb.cards.classes import BaseDuel, DuelCard, ElementalReactorSkill, UserAction, t_None

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class CriticalStrike(Skill):

19 associated_action = None

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

21 target = t_None()

22

23

24 class CriticalStrikeAction(GenericAction):

25 def apply_action(self):

26 tgt = self.target

27 ttags(tgt)['flan_cs'] = True

28 tgt.tags['flan_targets'] = []

29 return True

30

31

32 class CriticalStrikeLimit(ActionLimitExceeded):

33 pass

34

35

36 class CriticalStrikeHandler(THBEventHandler):

37 interested = ['action_apply', 'action_before', 'action_shootdown', 'action_stage_action']

38 execute_after = [

39 'AttackCardHandler',

40 'FrozenFrogHandler',

41 'ElementalReactorHandler',

42 'ReversedScalesHandler',

43 ]

44 execute_before = [

45 'MomijiShieldHandler',

46 'WineHandler',

47 ]

48

49 def handle(self, evt_type, act):

50 if evt_type == 'action_before' and isinstance(act, DrawCardStage):

51 if act.cancelled: return act

52 tgt = act.target

53 if not tgt.has_skill(CriticalStrike): return act

54 g = self.game

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

56 return act

57

58 g.process_action(CriticalStrikeAction(tgt, tgt))

59

60 act.amount = max(0, act.amount - 1)

61

62 elif evt_type == 'action_apply' and isinstance(act, BaseAttack):

63 src = act.source

64 tags = src.tags

65 if not self.in_critical_strike(src):

66 return act

67

68 tgt = act.target

69 if isinstance(act, BaseAttack):

70 tags['flan_targets'].append(tgt)

71 act.damage += 1

72

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

74 g = self.game

75 pact = g.action_stack[-1]

76 if not isinstance(pact, BaseDuel):

77 return act

78

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

80

81 if not self.in_critical_strike(src):

82 return act

83

84 act.amount += 1

85

86 elif evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard):

87 src = act.source

88 if not self.in_critical_strike(src):

89 return act

90

91 if act.card.is_card(AttackCard):

92 act._[self.__class__] = 'vitality-consumed'

93 src.tags['vitality'] -= 1

94

95 elif evt_type == 'action_shootdown':

96 if not isinstance(act, ActionStageLaunchCard): return act

97 c = act.card

98 src = act.source

99 tags = src.tags

100 if not self.in_critical_strike(src): return act

101 if not c.is_card(AttackCard): return act

102 if src.has_skill(ElementalReactorSkill): return act

103 if src.tags['vitality'] > 0: return act

104 if act._[self.__class__]: return act

105 if set(act.target_list) & set(tags['flan_targets']):

106 raise CriticalStrikeLimit

107

108 return act

109

110 elif evt_type == 'action_stage_action':

111 tgt = act

112 if not self.in_critical_strike(tgt): return act

113 AttackCardVitalityHandler.disable(tgt)

114

115 return act

116

117 def in_critical_strike(self, p):

118 g = self.game

119 try:

120 current = PlayerTurn.get_current(g).target

121 except IndexError:

122 return False

123

124 return (

125 ttags(p)['flan_cs'] and

126 current is p and

127 p.has_skill(CriticalStrike)

128 )

129

130

131 class Exterminate(Skill):

132 associated_action = None

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

134 target = t_None()

135

136

137 class ExterminateAction(UserAction):

138

139 def apply_action(self):

140 tgt = self.target

141 tgt.tags['exterminate'] = True

142 for s in tgt.skills:

143 if 'character' in s.skill_category:

144 tgt.disable_skill(s, 'exterminate')

145

146 return True

147

148

149 class ExterminateHandler(THBEventHandler):

150 interested = ['choose_target']

151

152 def handle(self, evt_type, arg):

153 if evt_type == 'choose_target':

154 act, tl = arg

155 src = act.source

156 g = self.game

157

158 if not src.has_skill(Exterminate):

159 return arg

160

161 c = act.card

162 if not c.is_card(AttackCard) and not c.is_card(DuelCard):

163 return arg

164

165 for tgt in tl:

166 g.process_action(ExterminateAction(src, tgt))

167

168 return arg

169

170

171@register_eh

172 class ExterminateFadeHandler(THBEventHandler):

173 interested = ['action_after', 'action_apply']

174

175 def handle(self, evt_type, arg):

176 if ((evt_type == 'action_after' and isinstance(arg, FinalizeStage)) or 176 ↛ 179 line 176 didn't jump to line 179, because the condition on line 176 was never true

177 (evt_type == 'action_apply' and isinstance(arg, PlayerDeath) and arg.target.has_skill(Exterminate))): # noqa

178

179 g = self.game

180 for p in g.players:

181 if p.tags.pop('exterminate', ''):

182 p.reenable_skill('exterminate')

183

184 return arg

185

186

187@register_character_to('common')

188 class Flandre(Character):

189 skills = [CriticalStrike, Exterminate]

190 eventhandlers = [CriticalStrikeHandler, ExterminateHandler]

191 maxlife = 4