Coverage for thb/characters/keine.py : 27%
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, PlayerTurn
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 card_usage = 'reforge'
21 def apply_action(self):
22 g = self.game
23 tgt = self.target
24 cl = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))
25 if cl:
26 c = cl[0]
27 else:
28 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
29 g.players.reveal(c)
30 if not c:
31 return False
33 g.process_action(Reforge(tgt, tgt, c))
34 return True
36 def cond(self, cards):
37 return len(cards) == 1 and cards[0].is_card(PhysicalCard)
40class TeachTargetActionStage(BaseActionStage):
41 one_shot = True
42 launch_card_cls = LaunchCard
45class TeachTargetEffect(GenericAction):
46 def __init__(self, source, target, card):
47 self.source = source
48 self.target = target
49 self.card = card
51 def apply_action(self):
52 tgt = self.target
53 c = self.card
54 g = self.game
55 tgt.reveal(c)
56 migrate_cards([c], tgt.cards, unwrap=True)
58 choice = g.user_input([tgt], ChooseOptionInputlet(self, ('reforge', 'action')))
59 if choice == 'reforge':
60 g.process_action(TeachTargetReforgeAction(tgt, tgt))
61 else:
62 act = TeachTargetActionStage(tgt)
63 g.process_action(act)
64 if act.action_count == 1:
65 return False
67 c = random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
68 if not c:
69 return False
71 g.players.reveal(c)
72 g.process_action(Reforge(tgt, tgt, c))
74 return True
77class TeachAction(UserAction):
78 no_reveal = True
79 card_usage = 'any'
81 def apply_action(self):
82 src, tgt = self.source, self.target
83 cl = VirtualCard.unwrap([self.associated_card])
84 assert len(cl) == 1
85 g = self.game
86 ttags(src)['teach_used'] = True
87 g.process_action(Reforge(src, src, cl[0]))
88 cl = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))
89 c = cl[0] if cl else random_choose_card(g, [src.cards, src.showncards, src.equips])
90 if not c:
91 return False
92 g.process_action(TeachTargetEffect(src, tgt, c))
93 return True
95 def cond(self, cl):
96 return len(cl) == 1 and not cl[0].is_card(VirtualCard)
98 def is_valid(self):
99 src = self.source
100 return not ttags(src)['teach_used']
103class Teach(Skill):
104 associated_action = TeachAction
105 skill_category = ['character', 'active']
106 no_drop = True
107 target = t_OtherOne()
108 usage = 'reforge'
110 def check(self):
111 acards = self.associated_cards
112 if not acards or len(acards) != 1:
113 return False
115 c = self.associated_cards[0]
116 return c.is_card(PhysicalCard)
119class KeineGuard(Skill):
120 associated_action = None
121 skill_category = ('character', 'passive', 'awake', 'once')
122 target = t_None()
125class KeineGuardAction(UserAction):
126 def apply_action(self):
127 src, tgt = self.source, self.target
128 g = self.game
129 g.process_action(MaxLifeChange(src, src, -1))
130 if src.dead:
131 return False
133 g.process_action(Heal(src, tgt))
135 try:
136 src.skills.remove(KeineGuard)
137 except ValueError:
138 pass
140 if tgt.life == min(p.life for p in g.players if not p.dead):
141 src.skills.append(Devoted)
142 src.tags['devoted'] = {
143 'to': tgt
144 }
145 tgt.skills.append(Devoted)
146 tgt.tags['devoted'] = {
147 'to': src
148 }
150 return True
153class Devoted(Skill):
154 associated_action = None
155 skill_category = ('character', 'passive', 'awake')
156 target = t_None()
159class DevotedDrawCards(DrawCards):
160 pass
163class DevotedHeal(Heal):
164 pass
167class DevotedHandler(THBEventHandler):
168 interested = ['action_before', 'action_after']
170 def handle(self, evt_type, act):
171 if evt_type == 'action_before' and isinstance(act, Damage):
172 tgt = act.target
173 if not tgt.has_skill(Devoted): return act
174 cp = tgt.tags['devoted']['to']
175 if cp.dead: return act
176 if cp.life <= tgt.life: return act
177 g = self.game
178 g.process_action(DevotedAction(cp, tgt, act))
180 elif evt_type == 'action_after' and isinstance(act, DrawCards):
181 tgt = act.target
182 if not tgt.has_skill(Devoted): return act
183 cp = tgt.tags['devoted']['to']
184 if cp.dead: return act
185 g = self.game
186 if PlayerTurn.get_current(g).target is not cp: return act
187 g.process_action(DevotedDrawCards(cp, amount=act.amount))
189 elif evt_type == 'action_after' and isinstance(act, Heal):
190 tgt = act.target
191 if not tgt.has_skill(Devoted): return act
192 cp = tgt.tags['devoted']['to']
193 if cp.dead: return act
194 g = self.game
195 if PlayerTurn.get_current(g).target is not cp: return act
196 g.process_action(DevotedHeal(tgt, cp, amount=act.amount))
198 return act
201class DevotedAction(UserAction):
202 def __init__(self, source, target, damage):
203 self.source = source
204 self.target = target
205 self.damage = damage
207 def apply_action(self):
208 src = self.source
209 dmg = self.damage
210 dmg.target = src
211 return True
214class KeineGuardHandler(THBEventHandler):
215 interested = ['action_apply']
217 def handle(self, evt_type, act):
218 if evt_type == 'action_apply' and isinstance(act, ActionStage):
219 src = act.target
220 g = self.game
222 candidates = list(p for p in g.players if not p.dead and p is not src and p.life < p.maxlife)
224 if not (src.has_skill(KeineGuard) and bool(candidates)):
225 return act
227 tl = user_choose_players(self, src, candidates)
228 if not tl:
229 return act
231 tgt = tl[0]
233 g.process_action(KeineGuardAction(src, tgt))
235 return act
237 def choose_player_target(self, tl):
238 if not tl:
239 return (tl, False)
241 return (tl[-1:], True)
244@register_character_to('common', '-kof')
245class Keine(Character):
246 skills = [Teach, KeineGuard]
247 eventhandlers_required = [KeineGuardHandler, DevotedHandler]
248 maxlife = 4