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

2from __future__ import annotations 

3 

4# -- stdlib -- 

5import itertools 

6 

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 

17 

18 

19# -- code -- 

20class DisarmHideAction(UserAction): 

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

22 self.source = source 

23 self.target = target 

24 self.cards = cards 

25 

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) 

33 

34 migrate_cards(self.cards, cl) 

35 return True 

36 

37 

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 

44 

45 

46class DisarmHandler(THBEventHandler): 

47 interested = ['action_after'] 

48 execute_after = ['DyingHandler'] 

49 

50 card_usage = 'launch' 

51 

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 

61 

62 assert isinstance(pact, THBAction) 

63 

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

65 return act 

66 

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

68 return act 

69 

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

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

72 

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] 

77 

78 l = sync_primitive(l, g.players.player) 

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

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

81 

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

86 

87 return act 

88 

89 

90class Disarm(Skill): 

91 associated_action = None 

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

93 target = t_None() 

94 

95 

96class SentryAction(AskForCard): 

97 card_usage = 'launch' 

98 

99 def __init__(self, source, target): 

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

101 self.victim = target 

102 

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

108 

109 

110class SentryHandler(THBEventHandler): 

111 interested = ['action_apply'] 

112 

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 

119 

120 tgt = act.target 

121 if p is tgt: continue 

122 self.target = tgt # for ui 

123 

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 

128 

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

130 

131 return act 

132 

133 

134class SentryAttack(TreatAs, VirtualCard): 

135 treat_as = AttackCard 

136 

137 

138class Sentry(Skill): 

139 associated_action = None 

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

141 target = t_None() 

142 

143 

144class TelegnosisHandler(THBEventHandler): 

145 interested = ['calcdistance'] 

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

147 

148 processing = False 

149 

150 def handle(self, evt_type, arg): 

151 if self.processing: 

152 return arg 

153 

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 

158 

159 g = self.game 

160 

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 

168 

169 finally: 

170 self.processing = False 

171 

172 return arg 

173 

174 

175class Telegnosis(Skill): 

176 associated_action = None 

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

178 target = t_None() 

179 

180 

181class SolidShieldHandler(THBEventHandler): 

182 interested = ['action_before'] 

183 execute_after = ['AttackCardHandler'] 

184 

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 

192 

193 if src is tgt: return act 

194 

195 if tgt.dead: return act 

196 

197 c = act.card 

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

199 return act 

200 

201 g = self.game 

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

203 if p is src: 

204 continue 

205 

206 if p is tgt: 

207 continue 

208 

209 if not p.has_skill(SolidShield): 

210 continue 

211 

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

213 if dist[tgt] > 0: 

214 continue 

215 

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 

220 

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

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

223 break 

224 

225 return act 

226 

227 

228class SolidShieldAction(UserAction): 

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

230 self.source = source 

231 self.target = target 

232 self.action = action 

233 

234 def apply_action(self): 

235 tgt = self.target 

236 lc = self.action 

237 assert isinstance(lc, ActionStageLaunchCard) 

238 

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) 

244 

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

246 lc.cancelled = True 

247 return True 

248 

249 

250class SolidShield(Skill): 

251 distance = 1 

252 associated_action = None 

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

254 target = t_None() 

255 

256 

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