# Coverage for thb/characters/keine.py : 52%

## 175 statements 107 run 68 missing 0 excluded 4 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# -- third party --

6# -- own --

7 from thb.actions import ActionStage, BaseActionStage, Damage, DrawCards, GenericAction, LaunchCard

8 from thb.actions import MaxLifeChange, Reforge, UserAction, migrate_cards, random_choose_card, ttags

9 from thb.actions import user_choose_cards, user_choose_players

10 from thb.cards.base import PhysicalCard, Skill, VirtualCard, t_None, t_OtherOne

11 from thb.cards.basic import Heal

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class TeachTargetReforgeAction(UserAction):

19 def apply_action(self):

20 g = self.game

21 tgt = self.target

22 c = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))

23 if c: 23 ↛ 26 line 23 didn't jump to line 26, because the condition on line 23 was never false

24 c = c[0]

25 else:

26 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

27 g.players.reveal(c)

28 if not c: 28 ↛ 29 line 28 didn't jump to line 29, because the condition on line 28 was never true

29 return False

30

31 g.process_action(Reforge(tgt, tgt, c))

32 return True

33

34 def cond(self, cards):

35 return len(cards) == 1 and cards[0].is_card(PhysicalCard)

36

37

38 class TeachTargetActionStage(BaseActionStage):

39 one_shot = True

40 launch_card_cls = LaunchCard

41

42

43 class TeachTargetEffect(GenericAction):

44 def __init__(self, source, target, card):

45 self.source = source

46 self.target = target

47 self.card = card

48

49 def apply_action(self):

50 tgt = self.target

51 c = self.card

52 g = self.game

53 tgt.reveal(c)

54 migrate_cards([c], tgt.cards, unwrap=True)

55

56 choice = g.user_input([tgt], ChooseOptionInputlet(self, ('reforge', 'action')))

57 if choice == 'reforge':

58 g.process_action(TeachTargetReforgeAction(tgt, tgt))

59 else:

60 act = TeachTargetActionStage(tgt)

61 g.process_action(act)

62 if act.action_count == 1:

63 return False

64

65 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

66 if not c: 66 ↛ 67 line 66 didn't jump to line 67, because the condition on line 66 was never true

67 return False

68

69 g.players.reveal(c)

70 g.process_action(Reforge(tgt, tgt, c))

71

72 return True

73

74

75 class TeachAction(UserAction):

76 no_reveal = True

77

78 def apply_action(self):

79 src, tgt = self.source, self.target

80 cl = VirtualCard.unwrap([self.associated_card])

81 assert len(cl) == 1

82 g = self.game

83 ttags(src)['teach_used'] = True

84 g.process_action(Reforge(src, src, cl[0]))

85 cl = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))

86 c = cl[0] if cl else random_choose_card(g, [src.cards, src.showncards, src.equips])

87 if not c: 87 ↛ 88 line 87 didn't jump to line 88, because the condition on line 87 was never true

88 return False

89 g.process_action(TeachTargetEffect(src, tgt, c))

90 return True

91

92 def cond(self, cl):

93 return len(cl) == 1 and not cl[0].is_card(VirtualCard)

94

95 def is_valid(self):

96 src = self.source

97 return not ttags(src)['teach_used']

98

99

100 class Teach(Skill):

101 associated_action = TeachAction

102 skill_category = ['character', 'active']

103 no_drop = True

104 target = t_OtherOne()

105 usage = 'reforge'

106

107 def check(self):

108 acards = self.associated_cards

109 if not acards or len(acards) != 1:

110 return False

111

112 c = self.associated_cards[0]

113 return c.is_card(PhysicalCard)

114

115

116 class KeineGuard(Skill):

117 associated_action = None

118 skill_category = ('character', 'passive', 'awake', 'once')

119 target = t_None()

120

121

122 class KeineGuardAction(UserAction):

123 def apply_action(self):

124 src, tgt = self.source, self.target

125 g = self.game

126 g.process_action(MaxLifeChange(src, src, -1))

127 if src.dead:

128 return False

129

130 g.process_action(Heal(src, tgt))

131

132 try:

133 src.skills.remove(KeineGuard)

134 except ValueError:

135 pass

136

137 if tgt.life == min(p.life for p in g.players if not p.dead):

138 src.skills.append(Devoted)

139 src.tags['devoted'] = {

140 'to': tgt

141 }

142 tgt.skills.append(Devoted)

143 tgt.tags['devoted'] = {

144 'to': src

145 }

146

147 return True

148

149

150 class Devoted(Skill):

151 associated_action = None

152 skill_category = ('character', 'passive', 'awake')

153 target = t_None()

154

155

156 class DevotedDrawCards(DrawCards):

157 pass

158

159

160 class DevotedHeal(Heal):

161 pass

162

163

164 class DevotedHandler(THBEventHandler):

165 interested = ('action_before', 'action_after')

166

167 def handle(self, evt_type, act):

168 if evt_type == 'action_before' and isinstance(act, Damage):

169 tgt = act.target

170 if not tgt.has_skill(Devoted): return act

171 cp = tgt.tags['devoted']['to']

172 if cp.dead: return act

173 if cp.life <= tgt.life: return act

174 g = self.game

175 g.process_action(DevotedAction(cp, tgt, act))

176

177 elif evt_type == 'action_after' and isinstance(act, DrawCards):

178 tgt = act.target

179 if not tgt.has_skill(Devoted): return act

180 cp = tgt.tags['devoted']['to']

181 if cp.dead: return act

182 g = self.game

183 if g.current_player is not cp: return act

184 g.process_action(DevotedDrawCards(cp, amount=act.amount))

185

186 elif evt_type == 'action_after' and isinstance(act, Heal):

187 tgt = act.target

188 if not tgt.has_skill(Devoted): return act

189 cp = tgt.tags['devoted']['to']

190 if cp.dead: return act

191 g = self.game

192 if g.current_player is not cp: return act

193 g.process_action(DevotedHeal(tgt, cp, amount=act.amount))

194

195 return act

196

197

198 class DevotedAction(UserAction):

199 def __init__(self, source, target, damage):

200 self.source = source

201 self.target = target

202 self.damage = damage

203

204 def apply_action(self):

205 src = self.source

206 dmg = self.damage

207 dmg.target = src

208 return True

209

210

211 class KeineGuardHandler(THBEventHandler):

212 interested = ('action_apply',)

213

214 def handle(self, evt_type, act):

215 if evt_type == 'action_apply' and isinstance(act, ActionStage):

216 src = act.target

217 g = self.game

218

219 candidates = list(p for p in g.players if not p.dead and p is not src and p.life < p.maxlife)

220

221 if not (src.has_skill(KeineGuard) and bool(candidates)):

222 return act

223

224 tl = user_choose_players(self, src, candidates)

225 if not tl:

226 return act

227

228 tgt = tl[0]

229

230 g.process_action(KeineGuardAction(src, tgt))

231

232 return act

233

234 def choose_player_target(self, tl):

235 if not tl:

236 return (tl, False)

237

238 return (tl[-1:], True)

239

240

241@register_character_to('common', '-kof')

242 class Keine(Character):

243 skills = [Teach, KeineGuard]

244 eventhandlers_required = [KeineGuardHandler, DevotedHandler]

245 maxlife = 4