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

## 177 statements 61 run 116 missing 0 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# -- third party --

6# -- own --

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

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 card_usage = 'reforge'

20

21 def apply_action(self):

22 g = self.game

23 tgt = self.target

24 cl = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))

25 if cl:

26 c = cl[0]

27 else:

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

29 g.players.reveal(c)

30 if not c:

31 return False

32

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

34 return True

35

36 def cond(self, cards):

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

38

39

40 class TeachTargetActionStage(BaseActionStage):

41 one_shot = True

42 launch_card_cls = LaunchCard

43

44

45 class TeachTargetEffect(GenericAction):

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

47 self.source = source

48 self.target = target

49 self.card = card

50

51 def apply_action(self):

52 tgt = self.target

53 c = self.card

54 g = self.game

55 tgt.reveal(c)

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

57

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

59 if choice == 'reforge':

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

61 else:

62 act = TeachTargetActionStage(tgt)

63 g.process_action(act)

64 if act.action_count == 1:

65 return False

66

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

68 if not c:

69 return False

70

71 g.players.reveal(c)

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

73

74 return True

75

76

77 class TeachAction(UserAction):

78 no_reveal = True

79 card_usage = 'any'

80

81 def apply_action(self):

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

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

84 assert len(cl) == 1

85 g = self.game

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

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

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

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

90 if not c:

91 return False

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

93 return True

94

95 def cond(self, cl):

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

97

98 def is_valid(self):

99 src = self.source

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

101

102

103 class Teach(Skill):

104 associated_action = TeachAction

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

106 no_drop = True

107 target = t_OtherOne()

108 usage = 'reforge'

109

110 def check(self):

111 acards = self.associated_cards

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

113 return False

114

115 c = self.associated_cards[0]

116 return c.is_card(PhysicalCard)

117

118

119 class KeineGuard(Skill):

120 associated_action = None

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

122 target = t_None()

123

124

125 class KeineGuardAction(UserAction):

126 def apply_action(self):

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

128 g = self.game

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

130 if src.dead:

131 return False

132

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

134

135 try:

136 src.skills.remove(KeineGuard)

137 except ValueError:

138 pass

139

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

141 src.skills.append(Devoted)

142 src.tags['devoted'] = {

143 'to': tgt

144 }

145 tgt.skills.append(Devoted)

146 tgt.tags['devoted'] = {

147 'to': src

148 }

149

150 return True

151

152

153 class Devoted(Skill):

154 associated_action = None

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

156 target = t_None()

157

158

159 class DevotedDrawCards(DrawCards):

160 pass

161

162

163 class DevotedHeal(Heal):

164 pass

165

166

167 class DevotedHandler(THBEventHandler):

168 interested = ['action_before', 'action_after']

169

170 def handle(self, evt_type, act):

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

172 tgt = act.target

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

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

175 if cp.dead: return act

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

177 g = self.game

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

179

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

181 tgt = act.target

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

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

184 if cp.dead: return act

185 g = self.game

186 if PlayerTurn.get_current(g).target is not cp: return act

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

188

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

190 tgt = act.target

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

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

193 if cp.dead: return act

194 g = self.game

195 if PlayerTurn.get_current(g).target is not cp: return act

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

197

198 return act

199

200

201 class DevotedAction(UserAction):

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

203 self.source = source

204 self.target = target

205 self.damage = damage

206

207 def apply_action(self):

208 src = self.source

209 dmg = self.damage

210 dmg.target = src

211 return True

212

213

214 class KeineGuardHandler(THBEventHandler):

215 interested = ['action_apply']

216

217 def handle(self, evt_type, act):

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

219 src = act.target

220 g = self.game

221

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

223

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

225 return act

226

227 tl = user_choose_players(self, src, candidates)

228 if not tl:

229 return act

230

231 tgt = tl[0]

232

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

234

235 return act

236

237 def choose_player_target(self, tl):

238 if not tl:

239 return (tl, False)

240

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

242

243

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

245 class Keine(Character):

246 skills = [Teach, KeineGuard]

247 eventhandlers_required = [KeineGuardHandler, DevotedHandler]

248 maxlife = 4