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

5# -- third party -- 

6# -- own -- 

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

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

9from thb.cards.base import Skill 

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

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

12from thb.characters.base import Character, register_character_to 

13from thb.inputlets import ChooseOptionInputlet 

14from thb.mode import THBEventHandler 

15 

16 

17# -- code -- 

18class CriticalStrike(Skill): 

19 associated_action = None 

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

21 target = t_None() 

22 

23 

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

32class CriticalStrikeLimit(ActionLimitExceeded): 

33 pass 

34 

35 

36class 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 51 ↛ exitline 51 didn't return from function 'handle', because the return on line 51 wasn't executed

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): 69 ↛ 115line 69 didn't jump to line 115, because the condition on line 69 was never false

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 

131class Exterminate(Skill): 

132 associated_action = None 

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

134 target = t_None() 

135 

136 

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

149class ExterminateHandler(THBEventHandler): 

150 interested = ['choose_target'] 

151 

152 def handle(self, evt_type, arg): 

153 if evt_type == 'choose_target': 153 ↛ 168line 153 didn't jump to line 168, because the condition on line 153 was never false

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 

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

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

188class Flandre(Character): 

189 skills = [CriticalStrike, Exterminate] 

190 eventhandlers = [CriticalStrikeHandler, ExterminateHandler] 

191 maxlife = 4