Coverage for thb/characters/yuuka.py : 33%
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 TYPE_CHECKING
7# -- third party --
8# -- own --
9from thb.actions import Damage, DrawCards, ForEach, LaunchCard, PlayerDeath, PlayerTurn, UserAction
10from thb.actions import user_choose_players
11from thb.cards.base import Skill
12from thb.cards.classes import AttackCard, Duel, InstantSpellCardAction, Reject, TreatAs, t_None
13from thb.characters.base import Character, register_character_to
14from thb.inputlets import ChooseOptionInputlet
15from thb.mode import THBEventHandler
17# -- typing --
18if TYPE_CHECKING:
19 from thb.thbkof import THBattleKOF # noqa: F401
22# -- code --
23class ReversedScales(TreatAs, Skill):
24 skill_category = ['character', 'active', 'compulsory']
25 treat_as = AttackCard
27 def check(self):
28 cl = self.associated_cards
29 if not cl or len(cl) != 1:
30 return False
32 g = self.game
33 if PlayerTurn.get_current(g).target is self.character:
34 return False
36 return cl[0].resides_in.type in ('cards', 'showncards')
39class Sadist(Skill):
40 associated_action = None
41 skill_category = ['character', 'passive']
42 target = t_None()
45class SadistKOF(Skill):
46 associated_action = None
47 skill_category = ['character', 'passive']
48 target = t_None()
51class SadistKOFDrawCards(DrawCards):
52 pass
55class SadistKOFDamageAction(UserAction):
56 def apply_action(self):
57 g = self.game
58 src, tgt = self.source, self.target
59 return g.process_action(Damage(src, tgt, 1))
62class SadistKOFHandler(THBEventHandler):
63 interested = ['action_after', 'character_debut']
64 execute_after = ['DeathHandler']
65 game: THBattleKOF
67 def handle(self, evt_type, arg):
68 if evt_type == 'action_after' and isinstance(arg, PlayerDeath):
69 tgt = arg.target
70 g = self.game
71 op = g.get_opponent(tgt)
72 if arg.source is op and op.has_skill(SadistKOF):
73 g.process_action(SadistKOFDrawCards(op, 2))
74 op.tags['sadist_kof_fire'] = True
76 elif evt_type == 'character_debut':
77 old, new = arg
78 if not old: return arg
80 g = self.game
81 op = g.get_opponent(new)
83 if op.has_skill(SadistKOF) and op.tags['sadist_kof_fire']:
84 op.tags['sadist_kof_fire'] = False
85 g.process_action(SadistKOFDamageAction(op, new))
87 return arg
90class ReversedScalesAction(UserAction):
91 def __init__(self, target, action):
92 self.source = target
93 self.target = target
94 self.action = action
96 def apply_action(self):
97 self.action.__class__ = Duel
98 return True
101class ReversedScalesHandler(THBEventHandler):
102 interested = ['action_before']
103 execute_before = ['MaidenCostumeHandler']
105 def handle(self, evt_type, act):
106 if evt_type != 'action_before':
107 return act
109 if not isinstance(act, InstantSpellCardAction):
110 return act
112 if isinstance(act, Reject):
113 # HACK
114 return act
116 if ForEach.is_group_effect(act):
117 return act
119 src = act.source
120 tgt = act.target
122 if not src or tgt is src:
123 return act
125 if not tgt.has_skill(ReversedScales):
126 return act
128 g = self.game
129 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
130 return act
132 g.process_action(ReversedScalesAction(tgt, act))
134 return act
137class SadistAction(UserAction):
138 def __init__(self, source, target):
139 self.source = source
140 self.target = target
142 def apply_action(self):
143 g = self.game
144 src, tgt = self.source, self.target
145 tgt.tags['sadist_target'] = False
146 g.process_action(Damage(src, tgt, 1))
147 return True
150class SadistHandler(THBEventHandler):
151 interested = ['action_after', 'action_before']
152 card_usage = 'drop'
153 execute_before = ['WineHandler']
154 execute_after = ['DeathHandler']
156 def handle(self, evt_type, act):
157 if evt_type == 'action_after' and isinstance(act, PlayerDeath):
158 src = act.source
159 if not src or not src.has_skill(Sadist):
160 return act
162 g = self.game
163 dist = LaunchCard.calc_distance(g, src, AttackCard())
164 candidates = [k for k, v in dist.items() if v <= 0 and k is not src]
166 if not candidates:
167 return act
169 pl = user_choose_players(self, src, candidates)
170 if pl:
171 self.game.process_action(SadistAction(src, pl[0]))
173 elif evt_type == 'action_before' and isinstance(act, Damage):
174 src = act.source
175 tgt = act.target
177 if not src or src is tgt:
178 return act
180 if not src.has_skill(Sadist):
181 return act
183 if tgt.life == 1:
184 act.amount += 1
186 return act
188 def choose_player_target(self, pl):
189 return pl[-1:], len(pl)
192@register_character_to('common', '-kof')
193class Yuuka(Character):
194 skills = [ReversedScales, Sadist]
195 eventhandlers = [ReversedScalesHandler, SadistHandler]
196 maxlife = 4
199@register_character_to('kof')
200class YuukaKOF(Character):
201 skills = [ReversedScales, SadistKOF]
202 eventhandlers = [ReversedScalesHandler, SadistKOFHandler]
203 maxlife = 4