Hide keyboard shortcuts

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 -*- 

2 

3# -- stdlib -- 

4import itertools 

5 

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 

16 

17 

18# -- code -- 

19class DisarmHideAction(UserAction): 

20 def __init__(self, source, target, cards): 

21 self.source = source 

22 self.target = target 

23 self.cards = cards 

24 

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) 

32 

33 migrate_cards(self.cards, cl) 

34 return True 

35 

36 

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 

43 

44 

45class DisarmHandler(THBEventHandler): 

46 interested = ('action_after',) 

47 execute_after = ('DyingHandler',) 

48 

49 card_usage = 'launch' 

50 

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

60 

61 if not pcard.is_card(AttackCard) and not (pcard.is_card(DuelCard) and pact.source is src): 

62 return act 

63 

64 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))): 

65 return act 

66 

67 cl = list(tgt.cards) + list(tgt.showncards) 

68 g.process_action(ShowCards(tgt, cl, [src])) 

69 

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] 

74 

75 l = sync_primitive(l, g.players) 

76 cl = list(itertools.compress(cl, l)) 

77 g.process_action(DisarmHideAction(src, tgt, cl)) 

78 

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)) 

83 

84 return act 

85 

86 

87class Disarm(Skill): 

88 associated_action = None 

89 skill_category = ['character', 'passive', 'compulsory'] 

90 target = t_None() 

91 

92 

93class SentryAction(AskForCard): 

94 card_usage = 'launch' 

95 

96 def __init__(self, source, target): 

97 AskForCard.__init__(self, source, source, AttackCard) 

98 self.victim = target 

99 

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)) 

105 

106 

107class SentryHandler(THBEventHandler): 

108 interested = ['action_apply'] 

109 

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 

116 

117 tgt = act.target 

118 if p is tgt: continue 

119 self.target = tgt # for ui 

120 

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 

125 

126 g.process_action(SentryAction(p, tgt)) 

127 

128 return act 

129 

130 

131class SentryAttack(TreatAs, VirtualCard): 

132 treat_as = AttackCard 

133 

134 

135class Sentry(Skill): 

136 associated_action = None 

137 skill_category = ['character', 'passive'] 

138 target = t_None() 

139 

140 

141class TelegnosisHandler(THBEventHandler): 

142 interested = ['calcdistance'] 

143 execute_after = ['AttackCardHandler', 'UFODistanceHandler'] 

144 

145 processing = False 

146 

147 def handle(self, evt_type, arg): 

148 if self.processing: 

149 return arg 

150 

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 

155 

156 g = self.game 

157 

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 

165 

166 finally: 

167 self.processing = False 

168 

169 return arg 

170 

171 

172class Telegnosis(Skill): 

173 associated_action = None 

174 skill_category = ['character', 'passive', 'compulsory'] 

175 target = t_None() 

176 

177 

178class SolidShieldHandler(THBEventHandler): 

179 interested = ['action_before'] 

180 execute_after = ['AttackCardHandler'] 

181 

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 

189 

190 if src is tgt: return act 

191 

192 if tgt.dead: return act 

193 

194 c = act.card 

195 if not (c.is_card(AttackCard) or 'instant_spellcard' in c.category): 

196 return act 

197 

198 g = self.game 

199 for p in g.players.rotate_to(src): 

200 if p is src: 

201 continue 

202 

203 if p is tgt: 

204 continue 

205 

206 if not p.has_skill(SolidShield): 

207 continue 

208 

209 dist = LaunchCard.calc_distance(g, p, SolidShield(p)) 

210 if dist[tgt] > 0: 

211 continue 

212 

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 

217 

218 if g.user_input([p], ChooseOptionInputlet(self, (False, True))): 

219 g.process_action(SolidShieldAction(p, src, act)) 

220 break 

221 

222 return act 

223 

224 

225class SolidShieldAction(UserAction): 

226 def __init__(self, source, target, action): 

227 self.source = source 

228 self.target = target 

229 self.action = action 

230 

231 def apply_action(self): 

232 tgt = self.target 

233 lc = self.action 

234 assert isinstance(lc, ActionStageLaunchCard) 

235 

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) 

241 

242 migrate_cards([lc.card], cl, unwrap=True) 

243 lc.cancelled = True 

244 return True 

245 

246 

247class SolidShield(Skill): 

248 distance = 1 

249 associated_action = None 

250 skill_category = ['character', 'passive', 'compulsory'] 

251 target = t_None() 

252 

253 

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