Coverage for thb/characters/momiji.py : 88%
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 -*-
3# -- stdlib --
4import itertools
6# -- third party --
7# -- own --
8from game.base import sync_primitive
9from thb.actions import ActionStage, ActionStageLaunchCard, AskForCard, Damage, FinalizeStage
10from thb.actions import GenericAction, LaunchCard, ShowCards, UserAction, migrate_cards, ttags
11from thb.cards.base import CardList, Skill, VirtualCard
12from thb.cards.classes import AttackCard, DollControlCard, DuelCard, TreatAs, t_None
13from thb.characters.base import Character, register_character_to
14from thb.inputlets import ChooseOptionInputlet
15from thb.mode import THBEventHandler
18# -- code --
19class DisarmHideAction(UserAction):
20 def __init__(self, source, target, cards):
21 self.source = source
22 self.target = target
23 self.cards = cards
25 def apply_action(self):
26 tgt = self.target
27 cl = getattr(tgt, 'momiji_sentry_cl', None)
28 if cl is None:
29 cl = CardList(tgt, 'momiji_sentry_cl')
30 tgt.momiji_sentry_cl = cl
31 tgt.showncardlists.append(cl)
33 migrate_cards(self.cards, cl)
34 return True
37class DisarmReturningAction(GenericAction):
38 def apply_action(self):
39 tgt = self.target
40 cl = getattr(tgt, 'momiji_sentry_cl', None)
41 cl and migrate_cards(cl, tgt.cards, unwrap=True)
42 return True
45class DisarmHandler(THBEventHandler):
46 interested = ('action_after',)
47 execute_after = ('DyingHandler',)
49 card_usage = 'launch'
51 def handle(self, evt_type, act):
52 if evt_type == 'action_after' and isinstance(act, Damage):
53 g = self.game
54 src, tgt = act.source, act.target
55 if not (src and src.has_skill(Disarm)): return act
56 if tgt.dead: return act
57 pact = g.action_stack[-1]
58 pcard = getattr(pact, 'associated_card', None)
59 if not pcard: return act 59 ↛ exitline 59 didn't return from function 'handle', because the return on line 59 wasn't executed
61 if not pcard.is_card(AttackCard) and not (pcard.is_card(DuelCard) and pact.source is src):
62 return act
64 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))):
65 return act
67 cl = list(tgt.cards) + list(tgt.showncards)
68 g.process_action(ShowCards(tgt, cl, [src]))
70 if g.is_server_side():
71 l = [bool(c.is_card(AttackCard) or 'spellcard' in c.category) for c in cl]
72 else:
73 l = [False for c in cl]
75 l = sync_primitive(l, g.players)
76 cl = list(itertools.compress(cl, l))
77 g.process_action(DisarmHideAction(src, tgt, cl))
79 elif evt_type == 'action_after' and isinstance(act, FinalizeStage):
80 tgt = act.target
81 g = self.game
82 g.process_action(DisarmReturningAction(tgt, tgt))
84 return act
87class Disarm(Skill):
88 associated_action = None
89 skill_category = ['character', 'passive', 'compulsory']
90 target = t_None()
93class SentryAction(AskForCard):
94 card_usage = 'launch'
96 def __init__(self, source, target):
97 AskForCard.__init__(self, source, source, AttackCard)
98 self.victim = target
100 def process_card(self, c):
101 g = self.game
102 src, tgt = self.source, self.victim
103 c = SentryAttack.wrap([c], src)
104 return g.process_action(LaunchCard(src, [tgt], c))
107class SentryHandler(THBEventHandler):
108 interested = ['action_apply']
110 def handle(self, evt_type, act):
111 if evt_type == 'action_apply' and isinstance(act, ActionStage):
112 g = self.game
113 for p in g.players:
114 if p.dead: continue
115 if not p.has_skill(Sentry): continue
117 tgt = act.target
118 if p is tgt: continue
119 self.target = tgt # for ui
121 dist = LaunchCard.calc_distance(g, p, AttackCard())
122 if dist.get(tgt, 1) > 0: continue
123 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):
124 continue
126 g.process_action(SentryAction(p, tgt))
128 return act
131class SentryAttack(TreatAs, VirtualCard):
132 treat_as = AttackCard
135class Sentry(Skill):
136 associated_action = None
137 skill_category = ['character', 'passive']
138 target = t_None()
141class TelegnosisHandler(THBEventHandler):
142 interested = ['calcdistance']
143 execute_after = ['AttackCardHandler', 'UFODistanceHandler']
145 processing = False
147 def handle(self, evt_type, arg):
148 if self.processing:
149 return arg
151 elif evt_type == 'calcdistance':
152 src, c, dist = arg
153 if not src.has_skill(Telegnosis): return arg
154 if not c.is_card(AttackCard): return arg
156 g = self.game
158 try:
159 self.processing = True
160 for p in dist:
161 if p is src: continue
162 d = LaunchCard.calc_distance(g, p, AttackCard())
163 if d[src] <= 0:
164 dist[p] = 0
166 finally:
167 self.processing = False
169 return arg
172class Telegnosis(Skill):
173 associated_action = None
174 skill_category = ['character', 'passive', 'compulsory']
175 target = t_None()
178class SolidShieldHandler(THBEventHandler):
179 interested = ['action_before']
180 execute_after = ['AttackCardHandler']
182 def handle(self, evt_type, act):
183 if evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard):
184 src, tgt = act.source, act.target
185 if not src or not tgt: return act 185 ↛ exitline 185 didn't return from function 'handle', because the return on line 185 wasn't executed
186 ttags(src)['solid_shield_launch_count'] += 1
187 if ttags(src)['solid_shield_launch_count'] != 1:
188 return act
190 if src is tgt: return act
192 if tgt.dead: return act
194 c = act.card
195 if not (c.is_card(AttackCard) or 'instant_spellcard' in c.category):
196 return act
198 g = self.game
199 for p in g.players.rotate_to(src):
200 if p is src:
201 continue
203 if p is tgt:
204 continue
206 if not p.has_skill(SolidShield):
207 continue
209 dist = LaunchCard.calc_distance(g, p, SolidShield(p))
210 if dist[tgt] > 0:
211 continue
213 cond = c.is_card(AttackCard) or 'instant_spellcard' in c.category
214 cond = cond and (c.is_card(DollControlCard) or len(act.target_list) == 1) # HACK HERE!
215 if not cond:
216 return act
218 if g.user_input([p], ChooseOptionInputlet(self, (False, True))):
219 g.process_action(SolidShieldAction(p, src, act))
220 break
222 return act
225class SolidShieldAction(UserAction):
226 def __init__(self, source, target, action):
227 self.source = source
228 self.target = target
229 self.action = action
231 def apply_action(self):
232 tgt = self.target
233 lc = self.action
234 assert isinstance(lc, ActionStageLaunchCard)
236 cl = getattr(tgt, 'momiji_sentry_cl', None)
237 if cl is None:
238 cl = CardList(tgt, 'momiji_sentry_cl')
239 tgt.momiji_sentry_cl = cl
240 tgt.showncardlists.append(cl)
242 migrate_cards([lc.card], cl, unwrap=True)
243 lc.cancelled = True
244 return True
247class SolidShield(Skill):
248 distance = 1
249 associated_action = None
250 skill_category = ['character', 'passive', 'compulsory']
251 target = t_None()
254@register_character_to('common')
255class Momiji(Character):
256 # skills = [Disarm, Sentry, Telegnosis]
257 skills = [Disarm, Sentry, SolidShield]
258 # eventhandlers = [SentryHandler, DisarmHandler, TelegnosisHandler]
259 eventhandlers = [SentryHandler, DisarmHandler, SolidShieldHandler]
260 maxlife = 4