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

2 

3# -- stdlib -- 

4from typing import Sequence, Any 

5# -- third party -- 

6# -- own -- 

7from thb.actions import ActionShootdown, Damage, GenericAction, LaunchCard, PrepareStage, UserAction 

8from thb.cards.base import Card, DummyCard, Skill, t_None 

9from thb.cards.classes import Attack, AttackCard, Heal, InevitableAttack 

10from thb.characters.base import Character, register_character_to 

11from thb.inputlets import ChooseOptionInputlet 

12from thb.mode import THBEventHandler 

13 

14 

15# -- code -- 

16class SpearTheGungnir(Skill): 

17 associated_action = None 

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

19 target = t_None() 

20 

21 

22class SpearTheGungnirAction(GenericAction): 

23 def __init__(self, act): 

24 self.act = act 

25 self.source = act.source 

26 self.target = act.target 

27 

28 def apply_action(self): 

29 self.act.__class__ = InevitableAttack 

30 return True 

31 

32 

33class SpearTheGungnirHandler(THBEventHandler): 

34 interested = ['action_before'] 

35 execute_before = ['ScarletRhapsodySwordHandler'] 

36 execute_after = ['HakuroukenEffectHandler', 'HouraiJewelHandler'] 

37 

38 def handle(self, evt_type, act): 

39 if evt_type == 'action_before' and isinstance(act, Attack): 

40 src = act.source 

41 if not src.has_skill(SpearTheGungnir): return act 

42 if isinstance(act, InevitableAttack): return act 42 ↛ exitline 42 didn't return from function 'handle', because the return on line 42 wasn't executed

43 

44 tgt = act.target 

45 

46 while True: 

47 if tgt.life > src.life: break 

48 if len(tgt.cards) + len(tgt.showncards) < len(src.cards) + len(src.showncards): break 

49 return act 

50 

51 g = self.game 

52 if g.user_input([act.source], ChooseOptionInputlet(self, (False, True))): 

53 g.process_action(SpearTheGungnirAction(act)) 

54 

55 return act 

56 

57 

58class VampireKiss(Skill): 

59 associated_action = None 

60 skill_category = ['character', 'passive', 'compulsory'] 

61 target = t_None() 

62 

63 

64class VampireKissAction(GenericAction): 

65 def apply_action(self): 

66 return self.game.process_action( 

67 Heal(self.target, self.source) 

68 ) 

69 

70 

71class VampireKissHandler(THBEventHandler): 

72 interested = ['action_apply', 'calcdistance'] 

73 

74 def handle(self, evt_type, act): 

75 if evt_type == 'action_apply' and isinstance(act, Damage): 

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

77 if not (src and src.has_skill(VampireKiss)): return act 

78 if src.life >= src.maxlife: return act 

79 g = self.game 

80 pact = g.action_stack[-1] 

81 if not isinstance(pact, Attack): return act 

82 card = pact.associated_card 

83 if (not card) or card.color != Card.RED: return act 

84 g.process_action(VampireKissAction(src, tgt)) 

85 

86 elif evt_type == 'calcdistance': 

87 src, card, dist = act 

88 

89 if not card.is_card(AttackCard): return act 

90 if not src.has_skill(VampireKiss): return act 

91 if card.color != Card.RED: return act 

92 

93 for p in dist: 

94 dist[p] -= 99999 

95 

96 return act 

97 

98 

99class ScarletMistAttackLimit(ActionShootdown): 

100 pass 

101 

102 

103class ScarletMistHandler(THBEventHandler): 

104 interested = [ 

105 'action_after', 

106 'action_apply', 

107 'action_shootdown', 

108 'post_calcdistance', 

109 ] 

110 

111 def handle(self, evt_type, act): 

112 if evt_type == 'action_shootdown' and isinstance(act, LaunchCard): 

113 if act.bypass_check: return act 

114 c = act.card 

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

116 

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

118 if not src.tags['scarlet_mist'] == 'nerf': 118 ↛ 121line 118 didn't jump to line 121, because the condition on line 118 was never false

119 return act 

120 

121 dist = LaunchCard.calc_raw_distance(src, DummyCard()) 

122 if dist[tgt] > 1: 

123 raise ScarletMistAttackLimit 

124 

125 elif evt_type == 'post_calcdistance': 

126 src, c, dist = act 

127 

128 if not c.is_card(AttackCard): 

129 return act 

130 

131 if src.tags['scarlet_mist'] != 'buff': 131 ↛ 134line 131 didn't jump to line 134, because the condition on line 131 was never false

132 return act 

133 

134 for k in dist: 

135 dist[k] = 0 

136 

137 elif evt_type == 'action_after' and isinstance(act, Damage): 

138 src = act.source 

139 if not (src and src.tags['scarlet_mist'] == 'buff'): return act 139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never false

140 if src.life >= src.maxlife: return act 

141 

142 g = self.game 

143 pact = g.action_stack[-1] 

144 if not isinstance(pact, Attack): return act 

145 if not pact.associated_card: return act 

146 

147 g.process_action(Heal(src, src, act.amount)) 

148 

149 elif evt_type == 'action_apply' and isinstance(act, PrepareStage): 

150 tgt = act.target 

151 if not tgt.has_skill(ScarletMist): return act 151 ↛ 152line 151 didn't jump to line 152, because the condition on line 151 was never false

152 if not tgt.tags['scarlet_mist']: return act 

153 g = self.game 

154 g.process_action(ScarletMistEndAction(tgt, tgt)) 

155 

156 return act 

157 

158 

159class ScarletMistAction(UserAction): 

160 def apply_action(self): 

161 src, tl = self.source, self.target_list 

162 src.tags['scarlet_mist_used'] = True 

163 g = self.game 

164 for p in g.players: 

165 p.tags['scarlet_mist'] = 'nerf' 

166 for p in tl: 

167 p.tags['scarlet_mist'] = 'buff' 

168 

169 return True 

170 

171 def is_valid(self): 

172 src = self.source 

173 return not src.tags['scarlet_mist_used'] 

174 

175 

176class ScarletMistEndAction(GenericAction): 

177 def apply_action(self): 

178 g = self.game 

179 for p in g.players: 

180 p.tags['scarlet_mist'] = False 

181 

182 return True 

183 

184 

185class ScarletMist(Skill): 

186 associated_action = ScarletMistAction 

187 skill_category = ['character', 'active', 'once', 'boss'] 

188 

189 def check(self) -> bool: 

190 return not len(self.associated_cards) 

191 

192 def target(self, src: Character, tl: Sequence[Character]): 

193 g = self.game 

194 from thb.thbrole import THBRoleRole 

195 n = sum(i == THBRoleRole.ACCOMPLICE for i in g.roles.values()) 

196 n -= sum(ch.dead and g.roles[ch.player] == THBRoleRole.ACCOMPLICE for ch in g.players) 

197 

198 tl = [t for t in tl if not t.dead] 

199 try: 

200 tl.remove(src) 

201 except ValueError: 

202 pass 

203 

204 tl.insert(0, src) 

205 return (tl[:n+1], bool(len(tl))) 

206 

207 

208@register_character_to('common', 'boss') 

209class Remilia(Character): 

210 skills = [SpearTheGungnir, VampireKiss] 

211 boss_skills = [ScarletMist] 

212 eventhandlers = [SpearTheGungnirHandler, VampireKissHandler, ScarletMistHandler] 

213 maxlife = 4