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

5from typing import Sequence, TYPE_CHECKING 

6 

7# -- third party -- 

8# -- own -- 

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

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

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

12from thb.characters.base import Character, register_character_to 

13from thb.inputlets import ChooseOptionInputlet 

14from thb.mode import THBEventHandler 

15 

16# -- typing -- 

17if TYPE_CHECKING: 

18 from thb.thbrole import THBattleRole # noqa: F401 

19 

20 

21# -- code -- 

22class SpearTheGungnir(Skill): 

23 associated_action = None 

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

25 target = t_None() 

26 

27 

28class SpearTheGungnirAction(GenericAction): 

29 def __init__(self, act): 

30 self.act = act 

31 self.source = act.source 

32 self.target = act.target 

33 

34 def apply_action(self): 

35 self.act.__class__ = InevitableAttack 

36 return True 

37 

38 

39class SpearTheGungnirHandler(THBEventHandler): 

40 interested = ['action_before'] 

41 execute_before = ['ScarletRhapsodySwordHandler'] 

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

43 

44 def handle(self, evt_type, act): 

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

46 src = act.source 

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

48 if isinstance(act, InevitableAttack): return act 

49 

50 tgt = act.target 

51 

52 while True: 

53 if tgt.life > src.life: break 

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

55 return act 

56 

57 g = self.game 

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

59 g.process_action(SpearTheGungnirAction(act)) 

60 

61 return act 

62 

63 

64class VampireKiss(Skill): 

65 associated_action = None 

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

67 target = t_None() 

68 

69 

70class VampireKissAction(GenericAction): 

71 def apply_action(self): 

72 return self.game.process_action( 

73 Heal(self.target, self.source) 

74 ) 

75 

76 

77class VampireKissHandler(THBEventHandler): 

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

79 

80 def handle(self, evt_type, act): 

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

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

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

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

85 g = self.game 

86 pact = g.action_stack[-1] 

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

88 card = pact.associated_card 

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

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

91 

92 elif evt_type == 'calcdistance': 

93 src, card, dist = act 

94 

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

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

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

98 

99 for p in dist: 

100 dist[p] -= 99999 

101 

102 return act 

103 

104 

105class ScarletMistAttackLimit(ActionShootdown): 

106 pass 

107 

108 

109class ScarletMistHandler(THBEventHandler): 

110 interested = [ 

111 'action_after', 

112 'action_apply', 

113 'action_shootdown', 

114 'post_calcdistance', 

115 ] 

116 

117 def handle(self, evt_type, act): 

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

119 if act.bypass_check: return act 

120 c = act.card 

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

122 

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

124 if not src.tags['scarlet_mist'] == 'nerf': 

125 return act 

126 

127 g = self.game 

128 dist = LaunchCard.calc_raw_distance(g, src, DummyCard()) 

129 if dist[tgt] > 1: 

130 raise ScarletMistAttackLimit 

131 

132 elif evt_type == 'post_calcdistance': 

133 src, c, dist = act 

134 

135 if not c.is_card(AttackCard): 

136 return act 

137 

138 if src.tags['scarlet_mist'] != 'buff': 

139 return act 

140 

141 for k in dist: 

142 dist[k] = 0 

143 

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

145 src = act.source 

146 if not (src and src.tags['scarlet_mist'] == 'buff'): return act 

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

148 

149 g = self.game 

150 pact = g.action_stack[-1] 

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

152 if not pact.associated_card: return act 

153 

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

155 

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

157 tgt = act.target 

158 if not tgt.has_skill(ScarletMist): return act 

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

160 g = self.game 

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

162 

163 return act 

164 

165 

166class ScarletMistAction(UserAction): 

167 def apply_action(self): 

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

169 src.tags['scarlet_mist_used'] = True 

170 g = self.game 

171 for p in g.players: 

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

173 for p in tl: 

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

175 

176 return True 

177 

178 def is_valid(self): 

179 src = self.source 

180 return not src.tags['scarlet_mist_used'] 

181 

182 

183class ScarletMistEndAction(GenericAction): 

184 def apply_action(self): 

185 g = self.game 

186 for p in g.players: 

187 p.tags['scarlet_mist'] = False 

188 

189 return True 

190 

191 

192class ScarletMist(Skill): 

193 associated_action = ScarletMistAction 

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

195 game: THBattleRole 

196 

197 def check(self) -> bool: 

198 return not len(self.associated_cards) 

199 

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

201 g = self.game 

202 from thb.thbrole import THBRoleRole 

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

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

205 

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

207 try: 

208 tl.remove(src) 

209 except ValueError: 

210 pass 

211 

212 tl.insert(0, src) 

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

214 

215 

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

217class Remilia(Character): 

218 skills = [SpearTheGungnir, VampireKiss] 

219 boss_skills = [ScarletMist] 

220 eventhandlers = [SpearTheGungnirHandler, VampireKissHandler, ScarletMistHandler] 

221 maxlife = 4