# Coverage for thb/characters/seiga.py : 25%

## 186 statements 61 run 125 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, ForEach, GenericAction, LaunchCard, LifeLost, PlayerDeath

8 from thb.actions import PlayerTurn, UserAction

9 from thb.cards.base import Skill

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

11 from thb.characters.base import Character, register_character_to

12 from thb.common import CharChoice

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler, THBAction

15 from thb.thbkof import KOFCharacterSwitchHandler, THBattleKOF

16

17

18# -- code --

19 class HeterodoxySkipAction(GenericAction):

20 def apply_action(self):

21 return True

22

23

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

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

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

124 class Summon(Skill):

125 associated_action = None

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

127 target = t_None()

128

129

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

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

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

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

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

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

273 class Seiga(Character):

274 skills = [Heterodoxy, Summon]

275 eventhandlers = [HeterodoxyHandler, SummonHandler]

276 maxlife = 4

277

278

279@register_character_to('kof')

280 class SeigaKOF(Character):

281 skills = [SummonKOF]

282 eventhandlers = [SummonKOFHandler]

283 maxlife = 4