# Coverage for thb/characters/reimu.py : 24%

## 184 statements 65 run 119 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 game.base import InterruptActionFlow

8 from thb.actions import ActionStage, AskForCard, Damage, DrawCards, FinalizeStage, LaunchCard

9 from thb.actions import PlayerRevive, PlayerTurn, UserAction, migrate_cards, ttags

10 from thb.cards.base import Card, PhysicalCard, Skill

11 from thb.cards.classes import AttackCard, GreenUFOSkill, RejectCard, TreatAs, UFOSkill, t_None

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class Flight(GreenUFOSkill):

19 skill_category = ['character', 'passive', 'compulsory']

20

21 @staticmethod

22 def increment(src):

23 for c in src.equips:

24 if issubclass(c.equipment_skill, UFOSkill):

25 return 0

26

27 return 1

28

29

30 class SpiritualAttack(TreatAs, Skill):

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

32 treat_as = RejectCard

33

34 def check(self):

35 cl = self.associated_cards

36 if not(cl and len(cl) == 1 and cl[0].color == Card.RED):

37 return False

38

39 c = cl[0]

40 if c.resides_in is None or c.resides_in.type not in (

41 'cards', 'showncards'

42 ): return False

43

44 return True

45

46

47 class TributeTarget(Skill):

48 associated_action = None

49 skill_category = ['character', 'passive', 'boss']

50 target = t_None()

51

52

53 class TributeAction(UserAction):

54 def apply_action(self):

55 tgt = self.target

56 migrate_cards([self.associated_card], tgt.showncards, unwrap=True)

57 src = self.source

58 src.tags['tribute_tag'] = src.tags['turn_count']

59 return True

60

61 def is_valid(self):

62 p = self.source

63

64 if p.tags.get('turn_count', 0) <= p.tags.get('tribute_tag', 0):

65 return False

66

67 tgt = self.target

68

69 if tgt.dead: return False

70 if len(tgt.cards) + len(tgt.showncards) >= tgt.maxlife: return False

71 return True

72

73

74 class Tribute(Skill):

75 associated_action = TributeAction

76 skill_category = ['active']

77 no_drop = True

78 usage = 'handover'

79

80 def check(self):

81 cl = self.associated_cards

82 rst = cl and len(cl) == 1 and (

83 cl[0].resides_in is not None and

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

85 cl[0].is_card(PhysicalCard)

86 )

87 return rst

88

89 def target(self, src, tl):

90 tl = [t for t in tl if not t.dead and t.has_skill(TributeTarget)]

91 try:

92 tl.remove(src)

93 except ValueError:

94 pass

95 return (tl[-1:], bool(len(tl)))

96

97

98 class TributeHandler(THBEventHandler):

99 interested = ['action_after', 'game_begin', 'switch_character']

100

101 def handle(self, evt_type, arg):

102 if evt_type == 'game_begin':

103 self.manage_tribute()

104

105 elif evt_type == 'switch_character':

106 self.manage_tribute()

107

108 elif evt_type == 'action_after' and isinstance(arg, PlayerRevive):

109 self.manage_tribute()

110

111 return arg

112

113 def manage_tribute(self):

114 g = self.game

115 cond = any([

116 isinstance(p, Character) and p.has_skill(TributeTarget)

117 for p in g.players

118 ])

119

120 if cond:

121 for p in g.players:

122 if not isinstance(p, Character): continue

123 if p.has_skill(TributeTarget): continue

124 if not p.has_skill(Tribute):

125 p.skills.append(Tribute)

126 else:

127 for p in g.players:

128 if not isinstance(p, Character): continue

129 try:

130 p.skills.remove(Tribute)

131 except ValueError:

132 pass

133

134

135# -----------------------------------------

136

137

138 class ReimuExterminate(Skill):

139 associated_action = None

140 skill_category = ['character', 'passive']

141 target = t_None()

142

143

144 class ReimuExterminateLaunchCard(LaunchCard):

145 def __init__(self, source, target, card, cause):

146 LaunchCard.__init__(self, source, [target], card, bypass_check=True)

147 self.cause = cause # for ui

148

149

150 class ReimuExterminateAction(AskForCard):

151 card_usage = 'launch'

152

153 def __init__(self, source, target, cause):

154 AskForCard.__init__(self, source, source, AttackCard)

155 self.victim = target

156 self.cause = cause # for ui

157

158 def process_card(self, c):

159 g = self.game

160 return g.process_action(ReimuExterminateLaunchCard(self.source, self.victim, c, self.cause))

161

162

163 class ReimuExterminateHandler(THBEventHandler):

164 interested = ['action_apply', 'action_after']

165 execute_after = [

166 'DyingHandler',

167 'CheatingHandler',

168 'IbukiGourdHandler',

169 'DisarmHandler',

170 'FreakingPowerHandler',

171 'LunaticHandler',

172 'AyaRoundfanHandler',

173 'NenshaPhoneHandler',

174 ]

175

176 def handle(self, evt_type, act):

177 if evt_type == 'action_apply' and isinstance(act, Damage):

178 if not act.source: return act

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

180 g = self.game

181

182 try:

183 current = PlayerTurn.get_current(g).target

184 except IndexError:

185 return act

186

187 if src is not current: return act

188 if src is tgt: return act

189 ttags(src)['did_damage'] = True

190

191 elif evt_type == 'action_after' and isinstance(act, Damage):

192 if not act.source: return act

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

194 g = self.game

195 try:

196 cur = PlayerTurn.get_current(g).target

197 except IndexError:

198 return act

199 if not cur: return act

200 if not tgt.has_skill(ReimuExterminate): return act

201 if cur.dead: return act

202 if cur is tgt: return act

203 g.process_action(ReimuExterminateAction(tgt, cur, 'damage'))

204

205 elif evt_type == 'action_apply' and isinstance(act, FinalizeStage):

206 tgt = act.target

207 if not ttags(tgt)['did_damage']:

208 return act

209

210 if tgt.dead:

211 return act

212

213 g = self.game

214 current = PlayerTurn.get_current(g).target

215 for actor in g.players.rotate_to(current):

216 if tgt is actor:

217 continue

218

219 if not actor.has_skill(ReimuExterminate):

220 continue

221

222 g.process_action(ReimuExterminateAction(actor, tgt, 'finalize'))

223

224 return act

225

226

227 class ReimuClear(Skill):

228 associated_action = None

229 skill_category = ['character', 'passive']

230 target = t_None()

231

232

233 class ReimuClearAction(UserAction):

234 def apply_action(self):

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

236 g = self.game

237 g.process_action(DrawCards(src, 1))

238 g.process_action(DrawCards(tgt, 1))

239 current = PlayerTurn.get_current(g).target

240 if current is src:

241 return True

242 else:

243 for act in reversed(g.action_stack):

244 if isinstance(act, ActionStage):

245 raise InterruptActionFlow(unwind_to=act)

246 else:

247 return True

248

249

250 class ReimuClearHandler(THBEventHandler):

251 interested = ['action_after']

252 execute_before = [

253 'AyaRoundfanHandler',

254 'NenshaPhoneHandler',

255 'DilemmaHandler',

256 'DecayDamageHandler',

257 'EchoHandler',

258 'MelancholyHandler',

259 'MajestyHandler',

260 'MasochistHandler',

261 ]

262

263 execute_after = ['IbukiGourdHandler']

264

265 def handle(self, evt_type, act):

266 if evt_type == 'action_after' and isinstance(act, Damage):

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

268 if not src: return act

269 if not src.has_skill(ReimuClear): return act

270 if src is tgt: return act

271 if src.dead or tgt.dead: return act

272

273 g = self.game

274 if g.user_input([src], ChooseOptionInputlet(self, (False, True))):

275 g.process_action(ReimuClearAction(src, tgt))

276

277 return act

278

279

280@register_character_to('common', 'boss')

281 class Reimu(Character):

282 # skills = [SealingArraySkill, Flight, TributeTarget]

283 # skills = [SpiritualAttack, Flight]

284 skills = [ReimuExterminate, ReimuClear]

285 boss_skills = [TributeTarget]

286 eventhandlers = [ReimuExterminateHandler, ReimuClearHandler, TributeHandler]

287 maxlife = 4