Coverage for thb/characters/momiji.py : 25%
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 --
5import itertools
7# -- third party --
8# -- own --
9from game.base import sync_primitive
10from thb.actions import ActionStage, ActionStageLaunchCard, AskForCard, Damage, FinalizeStage
11from thb.actions import GenericAction, LaunchCard, ShowCards, UserAction, migrate_cards, ttags
12from thb.cards.base import CardList, Skill, VirtualCard
13from thb.cards.classes import AttackCard, DollControlCard, DuelCard, TreatAs, t_None
14from thb.characters.base import Character, register_character_to
15from thb.inputlets import ChooseOptionInputlet
16from thb.mode import THBAction, THBEventHandler
19# -- code --
20class DisarmHideAction(UserAction):
21 def __init__(self, source, target, cards):
22 self.source = source
23 self.target = target
24 self.cards = cards
26 def apply_action(self):
27 tgt = self.target
28 cl = tgt._.get('momoji-sentry-cards')
29 if cl is None:
30 cl = CardList(tgt, 'momiji-sentry-cards')
31 tgt._['momiji-sentry-cards'] = cl
32 tgt.lists.append(cl)
34 migrate_cards(self.cards, cl)
35 return True
38class DisarmReturningAction(GenericAction):
39 def apply_action(self):
40 tgt = self.target
41 cl = tgt._.get('momiji-sentry-cards')
42 cl and migrate_cards(cl, tgt.cards, unwrap=True)
43 return True
46class DisarmHandler(THBEventHandler):
47 interested = ['action_after']
48 execute_after = ['DyingHandler']
50 card_usage = 'launch'
52 def handle(self, evt_type, act):
53 if evt_type == 'action_after' and isinstance(act, Damage):
54 g = self.game
55 src, tgt = act.source, act.target
56 if not (src and src.has_skill(Disarm)): return act
57 if tgt.dead: return act
58 pact = g.action_stack[-1]
59 pcard = getattr(pact, 'associated_card', None)
60 if not pcard: return act
62 assert isinstance(pact, THBAction)
64 if not pcard.is_card(AttackCard) and not (pcard.is_card(DuelCard) and pact.source is src):
65 return act
67 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))):
68 return act
70 cl = list(tgt.cards) + list(tgt.showncards)
71 g.process_action(ShowCards(tgt, cl, [src]))
73 if g.is_server_side():
74 l = [bool(c.is_card(AttackCard) or 'spellcard' in c.category) for c in cl]
75 else:
76 l = [False for c in cl]
78 l = sync_primitive(l, g.players.player)
79 cl = list(itertools.compress(cl, l))
80 g.process_action(DisarmHideAction(src, tgt, cl))
82 elif evt_type == 'action_after' and isinstance(act, FinalizeStage):
83 tgt = act.target
84 g = self.game
85 g.process_action(DisarmReturningAction(tgt, tgt))
87 return act
90class Disarm(Skill):
91 associated_action = None
92 skill_category = ['character', 'passive', 'compulsory']
93 target = t_None()
96class SentryAction(AskForCard):
97 card_usage = 'launch'
99 def __init__(self, source, target):
100 AskForCard.__init__(self, source, source, AttackCard)
101 self.victim = target
103 def process_card(self, c):
104 g = self.game
105 src, tgt = self.source, self.victim
106 c = SentryAttack.wrap([c], src)
107 return g.process_action(LaunchCard(src, [tgt], c))
110class SentryHandler(THBEventHandler):
111 interested = ['action_apply']
113 def handle(self, evt_type, act):
114 if evt_type == 'action_apply' and isinstance(act, ActionStage):
115 g = self.game
116 for p in g.players:
117 if p.dead: continue
118 if not p.has_skill(Sentry): continue
120 tgt = act.target
121 if p is tgt: continue
122 self.target = tgt # for ui
124 dist = LaunchCard.calc_distance(g, p, AttackCard())
125 if dist.get(tgt, 1) > 0: continue
126 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):
127 continue
129 g.process_action(SentryAction(p, tgt))
131 return act
134class SentryAttack(TreatAs, VirtualCard):
135 treat_as = AttackCard
138class Sentry(Skill):
139 associated_action = None
140 skill_category = ['character', 'passive']
141 target = t_None()
144class TelegnosisHandler(THBEventHandler):
145 interested = ['calcdistance']
146 execute_after = ['AttackCardHandler', 'UFODistanceHandler']
148 processing = False
150 def handle(self, evt_type, arg):
151 if self.processing:
152 return arg
154 elif evt_type == 'calcdistance':
155 src, c, dist = arg
156 if not src.has_skill(Telegnosis): return arg
157 if not c.is_card(AttackCard): return arg
159 g = self.game
161 try:
162 self.processing = True
163 for p in dist:
164 if p is src: continue
165 d = LaunchCard.calc_distance(g, p, AttackCard())
166 if d[src] <= 0:
167 dist[p] = 0
169 finally:
170 self.processing = False
172 return arg
175class Telegnosis(Skill):
176 associated_action = None
177 skill_category = ['character', 'passive', 'compulsory']
178 target = t_None()
181class SolidShieldHandler(THBEventHandler):
182 interested = ['action_before']
183 execute_after = ['AttackCardHandler']
185 def handle(self, evt_type, act):
186 if evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard):
187 src, tgt = act.source, act.target
188 if not src or not tgt: return act
189 ttags(src)['solid_shield_launch_count'] += 1
190 if ttags(src)['solid_shield_launch_count'] != 1:
191 return act
193 if src is tgt: return act
195 if tgt.dead: return act
197 c = act.card
198 if not (c.is_card(AttackCard) or 'instant_spellcard' in c.category):
199 return act
201 g = self.game
202 for p in g.players.rotate_to(src):
203 if p is src:
204 continue
206 if p is tgt:
207 continue
209 if not p.has_skill(SolidShield):
210 continue
212 dist = LaunchCard.calc_distance(g, p, SolidShield(p))
213 if dist[tgt] > 0:
214 continue
216 cond = c.is_card(AttackCard) or 'instant_spellcard' in c.category
217 cond = cond and (c.is_card(DollControlCard) or len(act.target_list) == 1) # HACK HERE!
218 if not cond:
219 return act
221 if g.user_input([p], ChooseOptionInputlet(self, (False, True))):
222 g.process_action(SolidShieldAction(p, src, act))
223 break
225 return act
228class SolidShieldAction(UserAction):
229 def __init__(self, source, target, action):
230 self.source = source
231 self.target = target
232 self.action = action
234 def apply_action(self):
235 tgt = self.target
236 lc = self.action
237 assert isinstance(lc, ActionStageLaunchCard)
239 cl = tgt._.get('momoji-sentry-cards')
240 if cl is None:
241 cl = CardList(tgt, 'momiji-sentry-cards')
242 tgt._['momiji-sentry-cards'] = cl
243 tgt.lists.append(cl)
245 migrate_cards([lc.card], cl, unwrap=True)
246 lc.cancelled = True
247 return True
250class SolidShield(Skill):
251 distance = 1
252 associated_action = None
253 skill_category = ['character', 'passive', 'compulsory']
254 target = t_None()
257@register_character_to('common')
258class Momiji(Character):
259 # skills = [Disarm, Sentry, Telegnosis]
260 skills = [Disarm, Sentry, SolidShield]
261 # eventhandlers = [SentryHandler, DisarmHandler, TelegnosisHandler]
262 eventhandlers = [SentryHandler, DisarmHandler, SolidShieldHandler]
263 maxlife = 4