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

## 176 statements 174 run 2 missing 0 excluded 4 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

15 from thb.thbkof import KOFCharacterSwitchHandler

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 card = self.associated_card.associated_cards[0]

53 src = self.source

54 victim = self.target

55 tgts = self.target_list[1:]

56

57 g.players.reveal(card)

58

59 if card.is_card(AttackCard):

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

61

62 # XXX: Use card owned by other

63 lc = LaunchCard(victim, tgts, card)

64

65 g = self.game

66 g.process_action(lc)

67

68 return True

69

70 def is_valid(self):

71 src = self.source

72 card = self.associated_card.associated_cards[0]

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

74 if not AttackCardVitalityHandler.is_disabled(src): 74 ↛ 77 line 74 didn't jump to line 77, because the condition on line 74 was never false

75 return False

76

77 if card.usage != 'launch': 77 ↛ 78 line 77 didn't jump to line 78, because the condition on line 77 was never true

78 return False

79

80 victim = self.target

81 tgts = self.target_list[1:]

82 lc = LaunchCard(victim, tgts, card)

83 return lc.can_fire()

84

85

86 class Heterodoxy(Skill):

87 no_drop = True

88 associated_action = HeterodoxyAction

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

90 usage = 'handover'

91

92 def check(self):

93 cl = self.associated_cards

94 return (

95 cl and len(cl) == 1 and

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

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

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

99 )

100

101 def target(self, src, tl):

102 cl = self.associated_cards

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

104 c = cl[0]

105 tname = c.target.__name__

106

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

108

109 if not tl: return [], False

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

111

112 if tname in ('t_Self', 't_All', 't_AllInclusive'): 112 ↛ 113 line 112 didn't jump to line 113, because the condition on line 112 was never true

113 return tl[-1:], True

114 else:

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

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

117

118

119 class Summon(Skill):

120 associated_action = None

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

122 target = t_None()

123

124

125 class SummonAction(UserAction):

126

127 def apply_action(self):

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

129

130 skills = tgt.skills

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

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

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

134

135 if not skills: return False 135 ↛ exit line 135 didn't return from function 'apply_action', because the return on line 135 wasn't executed

136

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

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

139

140 g = self.game

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

142

143 src.tags['summon_used'] = True

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

145 self.choice = mapping[choice]

146

147 return True

148

149

150 class SummonHandler(THBEventHandler):

151 interested = ['action_apply']

152 execute_after = ('DeathHandler', )

153

154 def handle(self, evt_type, act):

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

156 g = self.game

157 p = PlayerTurn.get_current(g).target

158 if p is act.target: return act

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

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

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

162

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

164

165 return act

166

167

168 class SummonKOFAction(UserAction):

169 def apply_action(self):

170 # WHOLE BUNCH OF MEGA HACK

171 old = self.target

172 g = self.game

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

174

175 ActionStage.force_break(g)

176

177 current = PlayerTurn.get_current(g).target

178 assert current is old

179

180 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler)

181 tgt = handler.switch(old)

182

183 tgt.life = old_life

184 tgt.maxlife += maxlife_delta

185

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

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

188 for i in list(s):

189 i.move_to(t)

190

191 for s in old.skills:

192 if 'character' not in s.skill_category:

193 tgt.skills.append(s)

194

195 for act in g.action_stack:

196 # Meh... good enough

197 if act.source is old:

198 act.source = tgt

199

200 if act.target is old:

201 act.target = tgt

202

203 if isinstance(act, LaunchCard):

204 act.target_list = [

205 tgt if p is old else p

206 for p in act.target_list

207 ]

208

209 tgt.tags = old.tags

210

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

212

213 if tgt.life > tgt.maxlife:

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

215

216 self.transition = [old, tgt]

217

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

219

220 return True

221

222

223 class SummonKOFCollect(UserAction):

224 def apply_action(self):

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

226 g = self.game

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

228 return True

229

230

231 class SummonKOFHandler(THBEventHandler):

232 interested = ['action_apply']

233

234 def handle(self, evt_type, act):

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

236 g = self.game

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

238 p = g.get_opponent(tgt)

239

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

241 return act

242

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

244

245 return act

246

247

248 class SummonKOF(Skill):

249 associated_action = SummonKOFAction

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

251 target = t_Self()

252

253 def check(self):

254 cl = self.associated_cards

255 return len(cl) == 0

256

257

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

259 class Seiga(Character):

260 skills = [Heterodoxy, Summon]

261 eventhandlers = [HeterodoxyHandler, SummonHandler]

262 maxlife = 4

263

264

265@register_character_to('kof')

266 class SeigaKOF(Character):

267 skills = [SummonKOF]

268 eventhandlers = [SummonKOFHandler]

269 maxlife = 4