Coverage for thb/characters/sp_flandre.py : 29%
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 absolute_import
4# -- stdlib --
5# -- third party --
6# -- own --
7from thb.actions import Damage, GenericAction, LaunchCard, LifeLost, MaxLifeChange, FinalizeStage
8from thb.actions import ttags, user_choose_players
9from thb.cards.base import Skill
10from thb.cards.classes import t_None
11from thb.characters.base import Character, register_character_to
12from thb.inputlets import ChooseOptionInputlet
13from thb.mode import THBEventHandler
16# -- code --
17class DestructionImpulse(Skill):
18 distance = 1
19 associated_action = None
20 skill_category = ['character', 'passive']
21 target = t_None()
24class DestructionImpulseAction(GenericAction):
25 def apply_action(self):
26 src = self.source
27 tgt = self.target
28 g = self.game
30 g.process_action(Damage(src, tgt))
31 g.process_action(LifeLost(src, src))
33 return True
36class DestructionImpulseHandler(THBEventHandler):
37 interested = ['action_after']
38 execute_before = ['CiguateraHandler']
40 def handle(self, evt_type, act):
41 if evt_type == 'action_after' and isinstance(act, Damage):
42 src = act.source
43 if not src: return act
44 if not src.has_skill(DestructionImpulse): return act
46 g = self.game
47 ttags(src)['destruction_tag'] = True
49 elif evt_type == 'action_after' and isinstance(act, FinalizeStage):
50 tgt = act.target
51 if not tgt.has_skill(DestructionImpulse): return act
53 g = self.game
54 if ttags(tgt)['destruction_tag']: return act
56 dist = LaunchCard.calc_distance(g, tgt, DestructionImpulse(tgt))
57 dist.pop(tgt, '')
59 for k in dist:
60 dist[k] = max(dist[k], 0)
62 nearest = min(dist.values())
63 candidates = [p for p, d in dist.items() if d == nearest]
64 candidates = [p for p in g.players if p in candidates] # order matters
66 if len(candidates) > 1:
67 pl = user_choose_players(self, tgt, candidates)
68 p = pl[0] if pl else candidates[0]
69 else:
70 p = candidates[0]
72 g.process_action(DestructionImpulseAction(tgt, p))
74 return act
76 def choose_player_target(self, tl):
77 if not tl: return (tl, False)
78 return (tl[-1:], True)
80 def cond(self, cards):
81 return not cards
84class FourOfAKind(Skill):
85 associated_action = None
86 skill_category = ['character', 'passive']
87 target = t_None()
90class FourOfAKindAction(GenericAction):
91 def __init__(self, source, target_act):
92 self.source = source
93 self.target_act = target_act
94 self.target = target_act.target
96 def apply_action(self):
97 g = self.game
98 src = self.source
99 self.target_act.cancelled = True
100 g.process_action(MaxLifeChange(src, src, -1))
102 return True
105class FourOfAKindHandler(THBEventHandler):
106 interested = ['action_before']
107 execute_before = ['WineHandler']
108 execute_after = [
109 'RepentanceStickHandler',
110 'DeathSickleHandler',
111 'CriticalStrikeHandler',
112 'SadistHandler',
113 'PerfectFreezeHandler',
114 ]
116 def handle(self, evt_type, act):
117 if evt_type == 'action_before' and isinstance(act, Damage):
118 if act.cancelled: return act
120 src = act.source
121 tgt = act.target
122 g = self.game
123 if tgt.has_skill(FourOfAKind) and act.amount <= tgt.life:
124 if g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
125 g.process_action(FourOfAKindAction(tgt, act))
127 if src and src.has_skill(FourOfAKind):
128 g = self.game
130 for a in reversed(g.action_stack):
131 if isinstance(a, LaunchCard):
132 break
133 else:
134 return act
136 if src.life == 1:
137 act.amount += 1
139 return act
142@register_character_to('common')
143class SpFlandre(Character):
144 skills = [DestructionImpulse, FourOfAKind]
145 eventhandlers = [DestructionImpulseHandler, FourOfAKindHandler]
146 maxlife = 4