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, ForEach, GenericAction, LaunchCard, LifeLost, PlayerDeath 

8from thb.actions import PlayerTurn, UserAction 

9from thb.cards.base import Skill 

10from thb.cards.classes import AttackCard, AttackCardVitalityHandler, t_None, t_Self 

11from thb.characters.base import Character, register_character_to 

12from thb.common import CharChoice 

13from thb.inputlets import ChooseOptionInputlet 

14from thb.mode import THBEventHandler, THBAction 

15from thb.thbkof import KOFCharacterSwitchHandler, THBattleKOF 

16 

17 

18# -- code -- 

19class HeterodoxySkipAction(GenericAction): 

20 def apply_action(self): 

21 return True 

22 

23 

24class HeterodoxyHandler(THBEventHandler): 

25 interested = ['action_before'] 

26 execute_before = ['MaidenCostumeHandler'] 

27 

28 def handle(self, evt_type, act): 

29 if evt_type == 'action_before' and ForEach.is_group_effect(act): 

30 tgt = act.target 

31 if not tgt.has_skill(Heterodoxy): return act 

32 

33 g = self.game 

34 for a in reversed(g.action_stack): 

35 if isinstance(a, HeterodoxyAction): 

36 break 

37 else: 

38 return act 

39 

40 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

41 return act 

42 

43 act.cancelled = True 

44 g.process_action(HeterodoxySkipAction(tgt, tgt)) 

45 

46 return act 

47 

48 

49class HeterodoxyAction(UserAction): 

50 def apply_action(self): 

51 g = self.game 

52 sk = self.associated_card 

53 assert isinstance(sk, Heterodoxy) 

54 card = sk.associated_cards[0] 

55 

56 src = self.source 

57 victim = self.target 

58 tgts = self.target_list[1:] 

59 

60 g.players.reveal(card) 

61 

62 if card.is_card(AttackCard): 

63 src.tags['vitality'] -= 1 

64 

65 # XXX: Use card owned by other 

66 lc = LaunchCard(victim, tgts, card) 

67 

68 g = self.game 

69 g.process_action(lc) 

70 

71 return True 

72 

73 def is_valid(self): 

74 src = self.source 

75 sk = self.associated_card 

76 assert isinstance(sk, Heterodoxy) 

77 card = sk.associated_cards[0] 

78 if card.is_card(AttackCard) and src.tags['vitality'] < 1: 

79 if not AttackCardVitalityHandler.is_disabled(src): 

80 return False 

81 

82 if card.usage != 'launch': 

83 return False 

84 

85 victim = self.target 

86 tgts = self.target_list[1:] 

87 lc = LaunchCard(victim, tgts, card) 

88 return lc.can_fire() 

89 

90 

91class Heterodoxy(Skill): 

92 no_drop = True 

93 associated_action = HeterodoxyAction 

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

95 usage = 'handover' 

96 

97 def check(self): 

98 cl = self.associated_cards 

99 return ( 

100 cl and len(cl) == 1 and 

101 cl[0].resides_in.type in ('cards', 'showncards') and 

102 not cl[0].is_card(Skill) and 

103 getattr(cl[0], 'associated_action', None) 

104 ) 

105 

106 def target(self, src, tl): 

107 cl = self.associated_cards 

108 if not cl: return ([], False) 

109 c = cl[0] 

110 tname = c.target.__name__ 

111 

112 tl = [t for t in tl if not t.dead] 

113 

114 if not tl: return [], False 

115 if tl[0] is self.character: return [], False 

116 

117 if tname in ('t_Self', 't_All', 't_AllInclusive'): 

118 return tl[-1:], True 

119 else: 

120 _tl, valid = c.target(tl[0], tl[1:]) 

121 return [tl[0]] + _tl, valid 

122 

123 

124class Summon(Skill): 

125 associated_action = None 

126 skill_category = ['character', 'passive', 'once'] 

127 target = t_None() 

128 

129 

130class SummonAction(UserAction): 

131 

132 def apply_action(self): 

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

134 

135 skills = tgt.skills 

136 skills = [s for s in skills if 'character' in s.skill_category] 

137 skills = [s for s in skills if 'once' not in s.skill_category] 

138 skills = [s for s in skills if 'awake' not in s.skill_category] 

139 

140 if not skills: return False 

141 

142 mapping = self.mapping = {s.__name__: s for s in skills} 

143 names = list(sorted(mapping.keys())) 

144 

145 g = self.game 

146 choice = g.user_input([src], ChooseOptionInputlet(self, names)) or names[0] 

147 

148 src.tags['summon_used'] = True 

149 src.skills.append(mapping[choice]) 

150 self.choice = mapping[choice] 

151 

152 return True 

153 

154 

155class SummonHandler(THBEventHandler): 

156 interested = ['action_apply'] 

157 execute_after = ['DeathHandler'] 

158 

159 def handle(self, evt_type, act): 

160 if evt_type == 'action_apply' and isinstance(act, PlayerDeath): 

161 g = self.game 

162 p = PlayerTurn.get_current(g).target 

163 if p is act.target: return act 

164 if not p.has_skill(Summon): return act 

165 if p.tags['summon_used']: return act 

166 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))): return act 

167 

168 g.process_action(SummonAction(p, act.target)) 

169 

170 return act 

171 

172 

173class SummonKOFAction(UserAction): 

174 game: THBattleKOF 

175 

176 def apply_action(self): 

177 # WHOLE BUNCH OF MEGA HACK 

178 old = self.target 

179 g = self.game 

180 old_life, maxlife_delta = old.life, old.maxlife - old.__class__.maxlife 

181 

182 ActionStage.force_break(g) 

183 

184 current = PlayerTurn.get_current(g).target 

185 assert current is old 

186 

187 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler) 

188 assert handler 

189 tgt = handler.switch(old) 

190 

191 tgt.life = old_life 

192 tgt.maxlife += maxlife_delta 

193 

194 for l in ('cards', 'showncards', 'equips', 'fatetell', 'special'): 

195 s, t = getattr(old, l), getattr(tgt, l) 

196 for i in list(s): 

197 i.move_to(t) 

198 

199 for s in old.skills: 

200 if 'character' not in s.skill_category: 

201 tgt.skills.append(s) 

202 

203 for act in g.action_stack: 

204 # Meh... good enough 

205 if not isinstance(act, THBAction): 

206 continue 

207 

208 if act.source is old: 

209 act.source = tgt 

210 

211 if act.target is old: 

212 act.target = tgt 

213 

214 if isinstance(act, LaunchCard): 

215 act.target_list = [ 

216 tgt if p is old else p 

217 for p in act.target_list 

218 ] 

219 

220 tgt.tags = old.tags 

221 

222 g.chosen[tgt.player].append(CharChoice(old.__class__)) 

223 

224 if tgt.life > tgt.maxlife: 

225 g.process_action(LifeLost(tgt, tgt, tgt.life - tgt.maxlife)) 

226 

227 self.transition = [old, tgt] 

228 

229 g.emit_event('character_debut', (old, tgt)) 

230 

231 return True 

232 

233 

234class SummonKOFCollect(UserAction): 

235 game: THBattleKOF 

236 

237 def apply_action(self): 

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

239 g = self.game 

240 g.chosen[src.player].append(CharChoice(tgt.__class__)) 

241 return True 

242 

243 

244class SummonKOFHandler(THBEventHandler): 

245 game: THBattleKOF 

246 interested = ['action_apply'] 

247 

248 def handle(self, evt_type, act): 

249 if evt_type == 'action_apply' and isinstance(act, PlayerDeath): 

250 g = self.game 

251 src, tgt = act.source, act.target 

252 p = g.get_opponent(tgt) 

253 

254 if not (src is p and p.has_skill(SummonKOF)): 

255 return act 

256 

257 g.process_action(SummonKOFCollect(p, tgt)) 

258 

259 return act 

260 

261 

262class SummonKOF(Skill): 

263 associated_action = SummonKOFAction 

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

265 target = t_Self() 

266 

267 def check(self): 

268 cl = self.associated_cards 

269 return len(cl) == 0 

270 

271 

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

273class Seiga(Character): 

274 skills = [Heterodoxy, Summon] 

275 eventhandlers = [HeterodoxyHandler, SummonHandler] 

276 maxlife = 4 

277 

278 

279@register_character_to('kof') 

280class SeigaKOF(Character): 

281 skills = [SummonKOF] 

282 eventhandlers = [SummonKOFHandler] 

283 maxlife = 4