Coverage for thb/characters/alice.py : 24%
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, THBattle
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:
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:
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:
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']
122 card_usage = 'reforge'
124 def handle(self, evt_type, act):
125 if evt_type == 'action_after' and isinstance(act, ActionStage):
126 tgt = act.target
127 if not tgt.has_skill(LittleLegion):
128 return act
130 c, = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips')) or (None,)
131 if c is None:
132 return act
134 g = self.game
136 assert isinstance(c, EquipmentCard)
137 category = c.equipment_category
139 g.process_action(Reforge(tgt, tgt, c))
141 if tgt.dead:
142 return act
144 if category == 'weapon':
145 g.process_action(LittleLegionAttackAction(tgt, tgt))
146 elif category == 'shield':
147 g.process_action(LittleLegionCoverAction(tgt, tgt))
148 elif category == 'accessories':
149 g.process_action(LittleLegionHoldAction(tgt, tgt))
150 elif category in ('redufo', 'greenufo'):
151 g.process_action(LittleLegionControlAction(tgt, tgt))
153 return act
155 def cond(self, cl):
156 if len(cl) != 1:
157 return False
159 c, = cl
160 if c.is_card(VirtualCard):
161 return False
163 return 'equipment' in c.category
166class DollBlast(Skill):
167 associated_action = None
168 skill_category = ['character', 'passive']
169 target = t_None()
172class DollBlastEffect(GenericAction):
173 def __init__(self, source, target, card, do_damage):
174 self.source = source
175 self.target = target
176 self.card = card
177 self.do_damage = do_damage
179 def apply_action(self):
180 g = self.game
181 src, tgt = self.source, self.target
182 g.process_action(DropCards(src, tgt, [self.card]))
183 if self.do_damage:
184 g.process_action(Damage(src, tgt, 1))
186 return True
189class DollBlastAction(UserAction):
190 def __init__(self, source, target, cards):
191 self.source = source
192 self.target = target
193 self.cards = cards
195 def apply_action(self):
196 g = self.game
197 cl = self.cards
198 track_ids = {c.track_id for c in cl}
200 src, tgt = self.source, self.target
201 for c in cl:
202 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards', 'equips')))
203 c = c or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])
204 if not c: return True
205 g.players.reveal(c)
206 g.process_action(DollBlastEffect(src, tgt, c, c.track_id in track_ids))
208 return True
211class DollBlastHandlerCommon(object):
212 game: THBattle
214 def fire(self, src, tgt, cards):
215 self.target = tgt # for ui
217 g = self.game
218 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))):
219 return
221 g.process_action(DollBlastAction(src, tgt, cards))
224class DollBlastMigrationHandler(DollBlastHandlerCommon, THBEventHandler):
225 interested = ['post_card_migration']
226 arbiter = PostCardMigrationHandler
227 game: THBattle
229 def handle(self, p, trans):
230 if not p.has_skill(DollBlast) or p.dead:
231 return True
233 equips = p.equips
235 assert isinstance(trans, MigrateCardsTransaction)
236 for m in trans.movements:
237 if m.fr is not equips:
238 continue
240 if not m.to.owner:
241 continue
243 tgt = m.to.owner
245 if tgt is m.fr.owner:
246 continue
248 if not (tgt.cards or tgt.showncards or tgt.equips):
249 continue
251 self.fire(p, tgt, [m.card])
253 return True
256class DollBlastDropHandler(DollBlastHandlerCommon, THBEventHandler):
257 interested = ['action_before', 'action_after']
258 game: THBattle
260 def handle(self, evt_type, act):
261 if evt_type == 'action_before' and isinstance(act, DropCards):
262 src, tgt = act.source, act.target
264 if not tgt.has_skill(DollBlast):
265 return act
267 if not src or src is tgt or not (src.cards or src.showncards or src.equips):
268 return act
270 if not any(c.resides_in.type == 'equips' for c in act.cards):
271 return act
273 act._['doll_blast'] = True
275 elif evt_type == 'action_after' and isinstance(act, DropCards) and act._['doll_blast']:
276 tgt = act.target
277 if tgt.has_skill(DollBlast):
278 self.fire(act.target, act.source, act.cards)
280 return act
283@register_character_to('common')
284class Alice(Character):
285 skills = [DollBlast, LittleLegion]
286 eventhandlers = [
287 DollBlastMigrationHandler,
288 DollBlastDropHandler,
289 LittleLegionHandler,
290 ]
291 maxlife = 4