Coverage for thb/characters/reimu.py : 70%
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
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
17# -- code --
18class Flight(GreenUFOSkill):
19 skill_category = ['character', 'passive', 'compulsory']
21 @staticmethod
22 def increment(src):
23 for c in src.equips:
24 if issubclass(c.equipment_skill, UFOSkill):
25 return 0
27 return 1
30class SpiritualAttack(TreatAs, Skill):
31 skill_category = ['character', 'active']
32 treat_as = RejectCard
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
39 c = cl[0]
40 if c.resides_in is None or c.resides_in.type not in (
41 'cards', 'showncards'
42 ): return False
44 return True
47class TributeTarget(Skill):
48 associated_action = None
49 skill_category = ['character', 'passive', 'boss']
50 target = t_None()
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
61 def is_valid(self):
62 p = self.source
64 if p.tags.get('turn_count', 0) <= p.tags.get('tribute_tag', 0):
65 return False
67 tgt = self.target
69 if tgt.dead: return False
70 if len(tgt.cards) + len(tgt.showncards) >= tgt.maxlife: return False
71 return True
74class Tribute(Skill):
75 associated_action = TributeAction
76 skill_category = ['active']
77 no_drop = True
78 usage = 'handover'
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
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)))
98class TributeHandler(THBEventHandler):
99 interested = ['action_after', 'game_begin', 'switch_character']
101 def handle(self, evt_type, arg):
102 if evt_type == 'game_begin':
103 self.manage_tribute()
105 elif evt_type == 'switch_character':
106 self.manage_tribute()
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()
111 return arg
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 ])
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
135# -----------------------------------------
138class ReimuExterminate(Skill):
139 associated_action = None
140 skill_category = ['character', 'passive']
141 target = t_None()
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
150class ReimuExterminateAction(AskForCard):
151 card_usage = 'launch'
153 def __init__(self, source, target, cause):
154 AskForCard.__init__(self, source, source, AttackCard)
155 self.victim = target
156 self.cause = cause # for ui
158 def process_card(self, c):
159 g = self.game
160 return g.process_action(ReimuExterminateLaunchCard(self.source, self.victim, c, self.cause))
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 )
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
181 try:
182 current = PlayerTurn.get_current(g).target
183 except IndexError:
184 return act
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
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 201 ↛ exitline 201 didn't return from function 'handle', because the return on line 201 wasn't executed
202 g.process_action(ReimuExterminateAction(tgt, cur, 'damage'))
204 elif evt_type == 'action_apply' and isinstance(act, FinalizeStage):
205 tgt = act.target
206 if not ttags(tgt)['did_damage']:
207 return act
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
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
218 if not actor.has_skill(ReimuExterminate):
219 continue
221 g.process_action(ReimuExterminateAction(actor, tgt, 'finalize'))
223 return act
226class ReimuClear(Skill):
227 associated_action = None
228 skill_category = ['character', 'passive']
229 target = t_None()
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): 242 ↛ 246line 242 didn't jump to line 246, because the loop on line 242 didn't complete
243 if isinstance(act, ActionStage):
244 raise InterruptActionFlow(unwind_to=act)
245 else:
246 return True
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 )
262 execute_after = (
263 'IbukiGourdHandler',
264 )
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
274 g = self.game
275 if g.user_input([src], ChooseOptionInputlet(self, (False, True))):
276 g.process_action(ReimuClearAction(src, tgt))
278 return act
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