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

5# -- third party -- 

6# -- own -- 

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

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

9from thb.actions import user_choose_cards, user_choose_players 

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

11from thb.cards.basic import Heal 

12from thb.characters.base import Character, register_character_to 

13from thb.inputlets import ChooseOptionInputlet 

14from thb.mode import THBEventHandler 

15 

16 

17# -- code -- 

18class 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 ↛ 24line 23 didn't jump to line 24, because the condition on line 23 was never true

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 ↛ 29line 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 

38class TeachTargetActionStage(BaseActionStage): 

39 one_shot = True 

40 launch_card_cls = LaunchCard 

41 

42 

43class 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 ↛ 67line 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 

75class 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 ↛ 88line 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 

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

116class KeineGuard(Skill): 

117 associated_action = None 

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

119 target = t_None() 

120 

121 

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

150class Devoted(Skill): 

151 associated_action = None 

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

153 target = t_None() 

154 

155 

156class DevotedDrawCards(DrawCards): 

157 pass 

158 

159 

160class DevotedHeal(Heal): 

161 pass 

162 

163 

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

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

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

242class Keine(Character): 

243 skills = [Teach, KeineGuard] 

244 eventhandlers_required = [KeineGuardHandler, DevotedHandler] 

245 maxlife = 4