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

## 149 statements 51 run 98 missing 2 excluded 0 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 from typing import Sequence, TYPE_CHECKING

6

7# -- third party --

8# -- own --

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

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

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

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16# -- typing --

17 if TYPE_CHECKING:

18 from thb.thbrole import THBattleRole # noqa: F401

19

20

21# -- code --

22 class SpearTheGungnir(Skill):

23 associated_action = None

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

25 target = t_None()

26

27

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

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

64 class VampireKiss(Skill):

65 associated_action = None

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

67 target = t_None()

68

69

70 class VampireKissAction(GenericAction):

71 def apply_action(self):

72 return self.game.process_action(

73 Heal(self.target, self.source)

74 )

75

76

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

105 class ScarletMistAttackLimit(ActionShootdown):

106 pass

107

108

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

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

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

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

217 class Remilia(Character):

218 skills = [SpearTheGungnir, VampireKiss]

219 boss_skills = [ScarletMist]

220 eventhandlers = [SpearTheGungnirHandler, VampireKissHandler, ScarletMistHandler]

221 maxlife = 4