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 game.base import InterruptActionFlow 

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

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

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

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

12from thb.characters.base import Character, register_character_to 

13from thb.inputlets import ChooseOptionInputlet 

14from thb.mode import THBEventHandler 

15 

16 

17# -- code -- 

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

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

47class TributeTarget(Skill): 

48 associated_action = None 

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

50 target = t_None() 

51 

52 

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

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

98class 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): 108 ↛ 109line 108 didn't jump to line 109, because the condition on line 108 was never true

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: 120 ↛ 121line 120 didn't jump to line 121, because the condition on line 120 was never true

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 128 ↛ 127line 128 didn't jump to line 127, because the continue on line 128 wasn't executed

129 try: 

130 p.skills.remove(Tribute) 

131 except ValueError: 

132 pass 

133 

134 

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

136 

137 

138class ReimuExterminate(Skill): 

139 associated_action = None 

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

141 target = t_None() 

142 

143 

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

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

163class ReimuExterminateHandler(THBEventHandler): 

164 interested = ('action_apply', 'action_after') 

165 execute_after = ('DyingHandler', 

166 'CheatingHandler', 

167 'IbukiGourdHandler', 

168 'DisarmHandler', 

169 'FreakingPowerHandler', 

170 'LunaticHandler', 

171 'AyaRoundfanHandler', 

172 'NenshaPhoneHandler', 

173 ) 

174 

175 def handle(self, evt_type, act): 

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

177 if not act.source: return act 

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

179 g = self.game 

180 

181 try: 

182 current = PlayerTurn.get_current(g).target 

183 except IndexError: 

184 return act 

185 

186 if src is not current: return act 

187 if src is tgt: return act 187 ↛ exitline 187 didn't return from function 'handle', because the return on line 187 wasn't executed

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

189 

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

191 if not act.source: return act 

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

193 g = self.game 

194 try: 

195 cur = PlayerTurn.get_current(g).target 

196 except IndexError: 

197 return act 

198 if not cur: return act 198 ↛ exitline 198 didn't return from function 'handle', because the return on line 198 wasn't executed

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

200 if cur.dead: return act 200 ↛ exitline 200 didn't return from function 'handle', because the return on line 200 wasn't executed

201 if cur is tgt: return act 

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

203 

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

205 tgt = act.target 

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

207 return act 

208 

209 if tgt.dead: 209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true

210 return act 

211 

212 g = self.game 

213 current = PlayerTurn.get_current(g).target 

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

215 if tgt is actor: 

216 continue 

217 

218 if not actor.has_skill(ReimuExterminate): 

219 continue 

220 

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

222 

223 return act 

224 

225 

226class ReimuClear(Skill): 

227 associated_action = None 

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

229 target = t_None() 

230 

231 

232class ReimuClearAction(UserAction): 

233 def apply_action(self): 

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

235 g = self.game 

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

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

238 current = PlayerTurn.get_current(g).target 

239 if current is src: 

240 return True 

241 else: 

242 for act in reversed(g.action_stack): 

243 if isinstance(act, ActionStage): 

244 raise InterruptActionFlow(unwind_to=act) 

245 else: 

246 return True 

247 

248 

249class ReimuClearHandler(THBEventHandler): 

250 interested = ('action_after',) 

251 execute_before = ( 

252 'AyaRoundfanHandler', 

253 'NenshaPhoneHandler', 

254 'DilemmaHandler', 

255 'DecayDamageHandler', 

256 'EchoHandler', 

257 'MelancholyHandler', 

258 'MajestyHandler', 

259 'MasochistHandler', 

260 ) 

261 

262 execute_after = ( 

263 'IbukiGourdHandler', 

264 ) 

265 

266 def handle(self, evt_type, act): 

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

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

269 if not src: return act 

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

271 if src is tgt: return act 271 ↛ exitline 271 didn't return from function 'handle', because the return on line 271 wasn't executed

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

273 

274 g = self.game 

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

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

277 

278 return act 

279 

280 

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

282class Reimu(Character): 

283 # skills = [SealingArraySkill, Flight, TributeTarget] 

284 # skills = [SpiritualAttack, Flight] 

285 skills = [ReimuExterminate, ReimuClear] 

286 boss_skills = [TributeTarget] 

287 eventhandlers = [ReimuExterminateHandler, ReimuClearHandler, TributeHandler] 

288 maxlife = 4