Coverage for thb/characters/flandre.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 --
5# -- third party --
6# -- own --
7from thb.actions import ActionStageLaunchCard, Damage, DrawCardStage, FinalizeStage, GenericAction
8from thb.actions import PlayerDeath, PlayerTurn, register_eh, ttags
9from thb.cards.base import Skill
10from thb.cards.classes import ActionLimitExceeded, AttackCard, AttackCardVitalityHandler, BaseAttack
11from thb.cards.classes import BaseDuel, DuelCard, ElementalReactorSkill, UserAction, t_None
12from thb.characters.base import Character, register_character_to
13from thb.inputlets import ChooseOptionInputlet
14from thb.mode import THBEventHandler
17# -- code --
18class CriticalStrike(Skill):
19 associated_action = None
20 skill_category = ['character', 'passive']
21 target = t_None()
24class CriticalStrikeAction(GenericAction):
25 def apply_action(self):
26 tgt = self.target
27 ttags(tgt)['flan_cs'] = True
28 tgt.tags['flan_targets'] = []
29 return True
32class CriticalStrikeLimit(ActionLimitExceeded):
33 pass
36class CriticalStrikeHandler(THBEventHandler):
37 interested = ['action_apply', 'action_before', 'action_shootdown', 'action_stage_action']
38 execute_after = [
39 'AttackCardHandler',
40 'FrozenFrogHandler',
41 'ElementalReactorHandler',
42 'ReversedScalesHandler',
43 ]
44 execute_before = [
45 'MomijiShieldHandler',
46 'WineHandler',
47 ]
49 def handle(self, evt_type, act):
50 if evt_type == 'action_before' and isinstance(act, DrawCardStage):
51 if act.cancelled: return act
52 tgt = act.target
53 if not tgt.has_skill(CriticalStrike): return act
54 g = self.game
55 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
56 return act
58 g.process_action(CriticalStrikeAction(tgt, tgt))
60 act.amount = max(0, act.amount - 1)
62 elif evt_type == 'action_apply' and isinstance(act, BaseAttack):
63 src = act.source
64 tags = src.tags
65 if not self.in_critical_strike(src):
66 return act
68 tgt = act.target
69 if isinstance(act, BaseAttack):
70 tags['flan_targets'].append(tgt)
71 act.damage += 1
73 elif evt_type == 'action_before' and isinstance(act, Damage):
74 g = self.game
75 pact = g.action_stack[-1]
76 if not isinstance(pact, BaseDuel):
77 return act
79 src, tgt = act.source, act.target
81 if not self.in_critical_strike(src):
82 return act
84 act.amount += 1
86 elif evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard):
87 src = act.source
88 if not self.in_critical_strike(src):
89 return act
91 if act.card.is_card(AttackCard):
92 act._[self.__class__] = 'vitality-consumed'
93 src.tags['vitality'] -= 1
95 elif evt_type == 'action_shootdown':
96 if not isinstance(act, ActionStageLaunchCard): return act
97 c = act.card
98 src = act.source
99 tags = src.tags
100 if not self.in_critical_strike(src): return act
101 if not c.is_card(AttackCard): return act
102 if src.has_skill(ElementalReactorSkill): return act
103 if src.tags['vitality'] > 0: return act
104 if act._[self.__class__]: return act
105 if set(act.target_list) & set(tags['flan_targets']):
106 raise CriticalStrikeLimit
108 return act
110 elif evt_type == 'action_stage_action':
111 tgt = act
112 if not self.in_critical_strike(tgt): return act
113 AttackCardVitalityHandler.disable(tgt)
115 return act
117 def in_critical_strike(self, p):
118 g = self.game
119 try:
120 current = PlayerTurn.get_current(g).target
121 except IndexError:
122 return False
124 return (
125 ttags(p)['flan_cs'] and
126 current is p and
127 p.has_skill(CriticalStrike)
128 )
131class Exterminate(Skill):
132 associated_action = None
133 skill_category = ['character', 'passive']
134 target = t_None()
137class ExterminateAction(UserAction):
139 def apply_action(self):
140 tgt = self.target
141 tgt.tags['exterminate'] = True
142 for s in tgt.skills:
143 if 'character' in s.skill_category:
144 tgt.disable_skill(s, 'exterminate')
146 return True
149class ExterminateHandler(THBEventHandler):
150 interested = ['choose_target']
152 def handle(self, evt_type, arg):
153 if evt_type == 'choose_target':
154 act, tl = arg
155 src = act.source
156 g = self.game
158 if not src.has_skill(Exterminate):
159 return arg
161 c = act.card
162 if not c.is_card(AttackCard) and not c.is_card(DuelCard):
163 return arg
165 for tgt in tl:
166 g.process_action(ExterminateAction(src, tgt))
168 return arg
171@register_eh
172class ExterminateFadeHandler(THBEventHandler):
173 interested = ['action_after', 'action_apply']
175 def handle(self, evt_type, arg):
176 if ((evt_type == 'action_after' and isinstance(arg, FinalizeStage)) or 176 ↛ 179line 176 didn't jump to line 179, because the condition on line 176 was never true
177 (evt_type == 'action_apply' and isinstance(arg, PlayerDeath) and arg.target.has_skill(Exterminate))): # noqa
179 g = self.game
180 for p in g.players:
181 if p.tags.pop('exterminate', ''):
182 p.reenable_skill('exterminate')
184 return arg
187@register_character_to('common')
188class Flandre(Character):
189 skills = [CriticalStrike, Exterminate]
190 eventhandlers = [CriticalStrikeHandler, ExterminateHandler]
191 maxlife = 4