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

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

40class TeachTargetActionStage(BaseActionStage): 

41 one_shot = True 

42 launch_card_cls = LaunchCard 

43 

44 

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

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

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

119class KeineGuard(Skill): 

120 associated_action = None 

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

122 target = t_None() 

123 

124 

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

153class Devoted(Skill): 

154 associated_action = None 

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

156 target = t_None() 

157 

158 

159class DevotedDrawCards(DrawCards): 

160 pass 

161 

162 

163class DevotedHeal(Heal): 

164 pass 

165 

166 

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

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

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

245class Keine(Character): 

246 skills = [Teach, KeineGuard] 

247 eventhandlers_required = [KeineGuardHandler, DevotedHandler] 

248 maxlife = 4