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): 

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 

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 = [ 

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 

227class ReimuClear(Skill): 

228 associated_action = None 

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

230 target = t_None() 

231 

232 

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

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

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