Coverage for thb/characters/shizuha.py : 25%
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 --
5from typing import cast
7# -- third party --
8# -- own --
9from thb.actions import Damage, DrawCards, DropCardStage, DropCards, FinalizeStage, GenericAction
10from thb.actions import MigrateCardsTransaction, PlayerTurn, UserAction, random_choose_card
11from thb.actions import user_choose_players
12from thb.cards.base import Skill, VirtualCard
13from thb.cards.classes import t_None
14from thb.characters.base import Character, register_character_to
15from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet
16from thb.mode import THBEventHandler
19# -- code --
20class AutumnWindEffect(GenericAction):
21 def apply_action(self):
22 src, tgt = self.source, self.target
24 g = self.game
26 catnames = ('cards', 'showncards', 'equips')
27 cats = [getattr(tgt, i) for i in catnames]
28 card = g.user_input([src], ChoosePeerCardInputlet(self, tgt, catnames))
29 card = card or random_choose_card(g, cats)
30 if not card:
31 return False
33 self.card = card
34 g.players.exclude(tgt).reveal(card)
35 g.process_action(DropCards(src, tgt, cards=[card]))
36 return True
38 def is_valid(self):
39 tgt = self.target
40 catnames = ('cards', 'showncards', 'equips')
41 return not tgt.dead and any(getattr(tgt, i) for i in catnames)
44class AutumnWindAction(UserAction):
45 def __init__(self, source, target_list):
46 self.source = source
47 self.target = source
48 self.target_list = target_list
50 def apply_action(self):
51 g = self.game
52 src = self.source
54 for p in self.target_list:
55 g.process_action(AutumnWindEffect(src, p))
57 return True
60class AutumnWindHandler(THBEventHandler):
61 interested = ['action_after']
63 def handle(self, evt_type, act):
64 if evt_type == 'action_after' and isinstance(act, DropCardStage):
65 self.n = n = act.dropn
66 if n <= 0:
67 return act
69 tgt = act.target
70 if not tgt.has_skill(AutumnWind):
71 return act
73 g = self.game
74 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
75 return act
77 candidates = [
78 p for p in g.players if
79 p is not tgt and
80 (p.cards or p.showncards or p.equips) and
81 not p.dead
82 ]
84 pl = candidates and user_choose_players(self, tgt, candidates)
85 if not pl:
86 return act
88 g.process_action(AutumnWindAction(tgt, pl))
90 return act
92 def choose_player_target(self, tl):
93 if not tl:
94 return (tl, False)
96 return (tl[:self.n], True)
99class AutumnWind(Skill):
100 associated_action = None
101 skill_category = ['character', 'passive']
102 target = t_None()
105class DecayDrawCards(DrawCards):
106 pass
109class DecayDrawCardHandler(THBEventHandler):
110 interested = ['post_card_migration']
111 execute_before = ['LuckHandler']
113 def handle(self, evt_type, arg):
114 if evt_type != 'post_card_migration':
115 return arg
117 g = self.game
119 try:
120 me = PlayerTurn.get_current(g).target
121 except IndexError:
122 return arg
124 if me is None: return arg
125 if me.dead: return arg
126 if not me.has_skill(Decay): return arg
128 trans = cast(MigrateCardsTransaction, arg)
130 candidates = {}
132 for m in trans.movements:
133 src = m.fr.owner
134 if not src: continue
135 if m.card.is_card(VirtualCard): continue
136 if m.fr.type not in ('cards', 'showncards'): continue
137 if src.cards or src.showncards: continue
138 if src.dead: continue
139 if src is me: continue
140 candidates[src] = 1
142 if not candidates:
143 return arg
145 g.process_action(DecayDrawCards(me, len(candidates)))
147 return arg
150class DecayAction(UserAction):
151 def apply_action(self):
152 tgt = self.target
153 tgt.tags['shizuha_decay'] = True
154 return True
157class DecayEffect(UserAction):
158 def __init__(self, source, target, dcs):
159 self.source = source
160 self.target = target
161 self.dcs = dcs
163 def apply_action(self):
164 self.dcs.dropn = max(1, self.dcs.dropn + 1)
165 return True
168class DecayDamageHandler(THBEventHandler):
169 interested = ['action_after', 'action_before']
170 execute_after = [
171 'DyingHandler',
172 'AyaRoundfanHandler',
173 'NenshaPhoneHandler',
174 'SuwakoHatHandler',
175 ]
177 def handle(self, evt_type, act):
178 if evt_type == 'action_after' and isinstance(act, Damage):
179 src, tgt = act.source, act.target
180 if not (tgt and tgt.has_skill(Decay)):
181 return act
183 g = self.game
185 try:
186 cur = PlayerTurn.get_current(g).target
187 except IndexError:
188 # Yugi AssaultKOF & Yuuka Sadist
189 return act
191 if cur is tgt: return act
192 g.process_action(DecayAction(src, cur))
194 elif evt_type == 'action_before' and isinstance(act, DropCardStage):
195 tgt = act.target
196 t = tgt.tags
197 if not t['shizuha_decay']: return act
199 t['shizuha_decay'] = False
200 g = self.game
201 g.process_action(DecayEffect(tgt, tgt, act))
203 return act
206class DecayFadeHandler(THBEventHandler):
207 interested = ['action_after']
209 def handle(self, evt_type, act):
210 if evt_type == 'action_after' and isinstance(act, FinalizeStage):
211 tgt = act.target
212 if tgt.tags['shizuha_decay']:
213 tgt.tags['shizuha_decay'] = False
215 return act
218class Decay(Skill):
219 associated_action = None
220 skill_category = ['character', 'passive']
221 target = t_None()
224@register_character_to('common')
225class Shizuha(Character):
226 skills = [Decay, AutumnWind]
227 eventhandlers = [DecayDamageHandler, DecayDrawCardHandler, DecayFadeHandler, AutumnWindHandler]
228 maxlife = 3