# Coverage for thb/characters/remilia.py : 69%

## 146 statements 106 run 40 missing 0 excluded 5 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

3# -- stdlib --

4 from typing import Sequence, Any

5# -- third party --

6# -- own --

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

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

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

10 from thb.characters.base import Character, register_character_to

11 from thb.inputlets import ChooseOptionInputlet

12 from thb.mode import THBEventHandler

13

14

15# -- code --

16 class SpearTheGungnir(Skill):

17 associated_action = None

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

19 target = t_None()

20

21

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

33 class 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 ↛ exit line 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

58 class VampireKiss(Skill):

59 associated_action = None

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

61 target = t_None()

62

63

64 class VampireKissAction(GenericAction):

65 def apply_action(self):

66 return self.game.process_action(

67 Heal(self.target, self.source)

68 )

69

70

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

99 class ScarletMistAttackLimit(ActionShootdown):

100 pass

101

102

103 class 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 ↛ 121 line 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 ↛ 134 line 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 ↛ 140 line 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 ↛ 152 line 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

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

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

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

209 class Remilia(Character):

210 skills = [SpearTheGungnir, VampireKiss]

211 boss_skills = [ScarletMist]

212 eventhandlers = [SpearTheGungnirHandler, VampireKissHandler, ScarletMistHandler]

213 maxlife = 4