Coverage for thb/characters/keine.py : 52%
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 ActionStage, BaseActionStage, Damage, DrawCards, GenericAction, LaunchCard
8from thb.actions import MaxLifeChange, Reforge, UserAction, migrate_cards, random_choose_card, ttags
9from thb.actions import user_choose_cards, user_choose_players
10from thb.cards.base import PhysicalCard, Skill, VirtualCard, t_None, t_OtherOne
11from thb.cards.basic import Heal
12from thb.characters.base import Character, register_character_to
13from thb.inputlets import ChooseOptionInputlet
14from thb.mode import THBEventHandler
17# -- code --
18class TeachTargetReforgeAction(UserAction):
19 def apply_action(self):
20 g = self.game
21 tgt = self.target
22 c = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))
23 if c: 23 ↛ 26line 23 didn't jump to line 26, because the condition on line 23 was never false
24 c = c[0]
25 else:
26 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
27 g.players.reveal(c)
28 if not c: 28 ↛ 29line 28 didn't jump to line 29, because the condition on line 28 was never true
29 return False
31 g.process_action(Reforge(tgt, tgt, c))
32 return True
34 def cond(self, cards):
35 return len(cards) == 1 and cards[0].is_card(PhysicalCard)
38class TeachTargetActionStage(BaseActionStage):
39 one_shot = True
40 launch_card_cls = LaunchCard
43class TeachTargetEffect(GenericAction):
44 def __init__(self, source, target, card):
45 self.source = source
46 self.target = target
47 self.card = card
49 def apply_action(self):
50 tgt = self.target
51 c = self.card
52 g = self.game
53 tgt.reveal(c)
54 migrate_cards([c], tgt.cards, unwrap=True)
56 choice = g.user_input([tgt], ChooseOptionInputlet(self, ('reforge', 'action')))
57 if choice == 'reforge':
58 g.process_action(TeachTargetReforgeAction(tgt, tgt))
59 else:
60 act = TeachTargetActionStage(tgt)
61 g.process_action(act)
62 if act.action_count == 1:
63 return False
65 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
66 if not c: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true
67 return False
69 g.players.reveal(c)
70 g.process_action(Reforge(tgt, tgt, c))
72 return True
75class TeachAction(UserAction):
76 no_reveal = True
78 def apply_action(self):
79 src, tgt = self.source, self.target
80 cl = VirtualCard.unwrap([self.associated_card])
81 assert len(cl) == 1
82 g = self.game
83 ttags(src)['teach_used'] = True
84 g.process_action(Reforge(src, src, cl[0]))
85 cl = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))
86 c = cl[0] if cl else random_choose_card(g, [src.cards, src.showncards, src.equips])
87 if not c: 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true
88 return False
89 g.process_action(TeachTargetEffect(src, tgt, c))
90 return True
92 def cond(self, cl):
93 return len(cl) == 1 and not cl[0].is_card(VirtualCard)
95 def is_valid(self):
96 src = self.source
97 return not ttags(src)['teach_used']
100class Teach(Skill):
101 associated_action = TeachAction
102 skill_category = ['character', 'active']
103 no_drop = True
104 target = t_OtherOne()
105 usage = 'reforge'
107 def check(self):
108 acards = self.associated_cards
109 if not acards or len(acards) != 1:
110 return False
112 c = self.associated_cards[0]
113 return c.is_card(PhysicalCard)
116class KeineGuard(Skill):
117 associated_action = None
118 skill_category = ('character', 'passive', 'awake', 'once')
119 target = t_None()
122class KeineGuardAction(UserAction):
123 def apply_action(self):
124 src, tgt = self.source, self.target
125 g = self.game
126 g.process_action(MaxLifeChange(src, src, -1))
127 if src.dead:
128 return False
130 g.process_action(Heal(src, tgt))
132 try:
133 src.skills.remove(KeineGuard)
134 except ValueError:
135 pass
137 if tgt.life == min(p.life for p in g.players if not p.dead):
138 src.skills.append(Devoted)
139 src.tags['devoted'] = {
140 'to': tgt
141 }
142 tgt.skills.append(Devoted)
143 tgt.tags['devoted'] = {
144 'to': src
145 }
147 return True
150class Devoted(Skill):
151 associated_action = None
152 skill_category = ('character', 'passive', 'awake')
153 target = t_None()
156class DevotedDrawCards(DrawCards):
157 pass
160class DevotedHeal(Heal):
161 pass
164class DevotedHandler(THBEventHandler):
165 interested = ('action_before', 'action_after')
167 def handle(self, evt_type, act):
168 if evt_type == 'action_before' and isinstance(act, Damage):
169 tgt = act.target
170 if not tgt.has_skill(Devoted): return act
171 cp = tgt.tags['devoted']['to']
172 if cp.dead: return act
173 if cp.life <= tgt.life: return act
174 g = self.game
175 g.process_action(DevotedAction(cp, tgt, act))
177 elif evt_type == 'action_after' and isinstance(act, DrawCards):
178 tgt = act.target
179 if not tgt.has_skill(Devoted): return act
180 cp = tgt.tags['devoted']['to']
181 if cp.dead: return act
182 g = self.game
183 if g.current_player is not cp: return act
184 g.process_action(DevotedDrawCards(cp, amount=act.amount))
186 elif evt_type == 'action_after' and isinstance(act, Heal):
187 tgt = act.target
188 if not tgt.has_skill(Devoted): return act
189 cp = tgt.tags['devoted']['to']
190 if cp.dead: return act
191 g = self.game
192 if g.current_player is not cp: return act
193 g.process_action(DevotedHeal(tgt, cp, amount=act.amount))
195 return act
198class DevotedAction(UserAction):
199 def __init__(self, source, target, damage):
200 self.source = source
201 self.target = target
202 self.damage = damage
204 def apply_action(self):
205 src = self.source
206 dmg = self.damage
207 dmg.target = src
208 return True
211class KeineGuardHandler(THBEventHandler):
212 interested = ('action_apply',)
214 def handle(self, evt_type, act):
215 if evt_type == 'action_apply' and isinstance(act, ActionStage):
216 src = act.target
217 g = self.game
219 candidates = list(p for p in g.players if not p.dead and p is not src and p.life < p.maxlife)
221 if not (src.has_skill(KeineGuard) and bool(candidates)):
222 return act
224 tl = user_choose_players(self, src, candidates)
225 if not tl:
226 return act
228 tgt = tl[0]
230 g.process_action(KeineGuardAction(src, tgt))
232 return act
234 def choose_player_target(self, tl):
235 if not tl:
236 return (tl, False)
238 return (tl[-1:], True)
241@register_character_to('common', '-kof')
242class Keine(Character):
243 skills = [Teach, KeineGuard]
244 eventhandlers_required = [KeineGuardHandler, DevotedHandler]
245 maxlife = 4