Coverage for thb/characters/alice.py : 94%
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 -*-
3from typing import Sequence, Tuple, List
4# -- stdlib --
5# -- third party --
6# -- own --
7from thb.actions import ActionStage, Damage, DrawCards, DropCardStage, DropCards, GenericAction
8from thb.actions import LaunchCard, PlayerTurn, PostCardMigrationHandler, Reforge, UserAction, MigrateCardsTransaction
9from thb.actions import random_choose_card, user_choose_cards, user_choose_players
10from thb.cards.base import Skill, VirtualCard
11from thb.cards.definition import EquipmentCard
12from thb.cards.classes import AttackCard, DollControlCard, Heal, TreatAs, t_None
13from thb.characters.base import Character, register_character_to
14from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet
15from thb.mode import THBEventHandler
18# -- code --
19class LittleLegionAttackCard(TreatAs, VirtualCard):
20 treat_as = AttackCard
23class LittleLegionDollControlCard(TreatAs, VirtualCard):
24 treat_as = DollControlCard
27class LittleLegion(Skill):
28 associated_action = None
29 skill_category = ['character', 'passive']
30 target = t_None()
33class LittleLegionAttackAction(UserAction):
34 def apply_action(self):
35 g = self.game
36 src = self.source
37 pl = [p for p in g.players if not p.dead and p is not src]
38 if not pl: 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true
39 return False
41 victim, = user_choose_players(self, src, pl) or (None,)
42 if victim is None:
43 return False
45 lc = LaunchCard(src, [victim], LittleLegionAttackCard(src), bypass_check=True)
46 g.process_action(lc)
47 return True
49 def choose_player_target(self, tl):
50 if not tl: 50 ↛ 51line 50 didn't jump to line 51, because the condition on line 50 was never true
51 return (tl, False)
53 return (tl[-1:], True)
56class LittleLegionCoverEffect(Heal):
57 pass
60class LittleLegionCoverAction(UserAction):
61 def apply_action(self):
62 g = self.game
63 src = self.source
64 pl = [p for p in g.players if not p.dead and p.life < p.maxlife]
65 if not pl:
66 return False
68 beneficiary, = user_choose_players(self, src, pl) or (None,)
69 if beneficiary is None:
70 return False
72 g.process_action(LittleLegionCoverEffect(src, beneficiary, 1))
73 return True
75 def choose_player_target(self, tl):
76 if not tl: 76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true
77 return (tl, False)
79 return (tl[-1:], True)
82class LittleLegionHoldAction(UserAction):
83 def apply_action(self):
84 src = self.source
86 g = self.game
87 g.process_action(DrawCards(src, 1))
89 turn = PlayerTurn.get_current(g)
90 try:
91 turn.pending_stages.remove(DropCardStage)
92 except Exception:
93 pass
95 return True
98class LittleLegionControlAction(UserAction):
99 def apply_action(self) -> bool:
100 g = self.game
101 src = self.source
102 pl = [p for p in g.players if not p.dead]
103 rst = user_choose_players(self, src, pl)
105 if not rst:
106 return False
108 attacker, victim = rst
109 self.target_list = attacker, victim
111 g.process_action(LaunchCard(src, [attacker, victim], LittleLegionDollControlCard(attacker)))
112 return True
114 def choose_player_target(self, tl: Sequence[Character]) -> Tuple[List[Character], bool]:
115 src = self.source
116 trimmed, rst = DollControlCard.target(None, src, tl)
117 return trimmed, rst and LaunchCard(src, trimmed, LittleLegionDollControlCard(src)).can_fire()
120class LittleLegionHandler(THBEventHandler):
121 interested = ['action_after']
123 def handle(self, evt_type, act):
124 if evt_type == 'action_after' and isinstance(act, ActionStage):
125 tgt = act.target
126 if not tgt.has_skill(LittleLegion):
127 return act
129 c, = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips')) or (None,)
130 if c is None:
131 return act
133 g = self.game
135 assert isinstance(c, EquipmentCard)
136 category = c.equipment_category
138 g.process_action(Reforge(tgt, tgt, c))
140 if tgt.dead: 140 ↛ 141line 140 didn't jump to line 141, because the condition on line 140 was never true
141 return act
143 if category == 'weapon':
144 g.process_action(LittleLegionAttackAction(tgt, tgt))
145 elif category == 'shield':
146 g.process_action(LittleLegionCoverAction(tgt, tgt))
147 elif category == 'accessories':
148 g.process_action(LittleLegionHoldAction(tgt, tgt))
149 elif category in ('redufo', 'greenufo'): 149 ↛ 152line 149 didn't jump to line 152, because the condition on line 149 was never false
150 g.process_action(LittleLegionControlAction(tgt, tgt))
152 return act
154 def cond(self, cl):
155 if len(cl) != 1:
156 return False
158 c, = cl
159 if c.is_card(VirtualCard):
160 return False
162 return 'equipment' in c.category
165class DollBlast(Skill):
166 associated_action = None
167 skill_category = ['character', 'passive']
168 target = t_None()
171class DollBlastEffect(GenericAction):
172 def __init__(self, source, target, card, do_damage):
173 self.source = source
174 self.target = target
175 self.card = card
176 self.do_damage = do_damage
178 def apply_action(self):
179 g = self.game
180 src, tgt = self.source, self.target
181 g.process_action(DropCards(src, tgt, [self.card]))
182 if self.do_damage:
183 g.process_action(Damage(src, tgt, 1))
185 return True
188class DollBlastAction(UserAction):
189 def __init__(self, source, target, cards):
190 self.source = source
191 self.target = target
192 self.cards = cards
194 def apply_action(self):
195 g = self.game
196 cl = self.cards
197 track_ids = {c.track_id for c in cl}
199 src, tgt = self.source, self.target
200 for c in cl:
201 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards', 'equips')))
202 c = c or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
203 if not c: return True 203 ↛ exitline 203 didn't return from function 'apply_action', because the return on line 203 wasn't executed
204 g.players.reveal(c)
205 g.process_action(DollBlastEffect(src, tgt, c, c.track_id in track_ids))
207 return True
210class DollBlastHandlerCommon(object):
212 def fire(self, src, tgt, cards):
213 self.target = tgt # for ui
215 g = self.game
216 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))):
217 return
219 g.process_action(DollBlastAction(src, tgt, cards))
222class DollBlastMigrationHandler(DollBlastHandlerCommon, THBEventHandler):
223 interested = ['post_card_migration']
224 arbiter = PostCardMigrationHandler
226 def handle(self, p, trans):
227 if not p.has_skill(DollBlast) or p.dead:
228 return True
230 equips = p.equips
232 assert isinstance(trans, MigrateCardsTransaction)
233 for m in trans.movements:
234 if m.fr is not equips:
235 continue
237 if not m.to.owner:
238 continue
240 tgt = m.to.owner
242 if tgt is m.fr.owner:
243 continue
245 if not (tgt.cards or tgt.showncards or tgt.equips): 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true
246 continue
248 self.fire(p, tgt, [m.card])
250 return True
253class DollBlastDropHandler(DollBlastHandlerCommon, THBEventHandler):
254 interested = ['action_before', 'action_after']
256 def handle(self, evt_type, act):
257 if evt_type == 'action_before' and isinstance(act, DropCards):
258 src, tgt = act.source, act.target
260 if not tgt.has_skill(DollBlast):
261 return act
263 if not src or src is tgt or not (src.cards or src.showncards or src.equips):
264 return act
266 if not any(c.resides_in.type == 'equips' for c in act.cards):
267 return act
269 act._['doll_blast'] = True
271 elif evt_type == 'action_after' and isinstance(act, DropCards) and act._['doll_blast']:
272 tgt = act.target
273 if tgt.has_skill(DollBlast): 273 ↛ 276line 273 didn't jump to line 276, because the condition on line 273 was never false
274 self.fire(act.target, act.source, act.cards)
276 return act
279@register_character_to('common')
280class Alice(Character):
281 skills = [DollBlast, LittleLegion]
282 eventhandlers = [
283 DollBlastMigrationHandler,
284 DollBlastDropHandler,
285 LittleLegionHandler,
286 ]
287 maxlife = 4