# Coverage for thb/characters/momiji.py : 88%

## 182 statements 166 run 16 missing 0 excluded 2 partial

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

4 import itertools

5

6# -- third party --

7# -- own --

8 from game.base import sync_primitive

9 from thb.actions import ActionStage, ActionStageLaunchCard, AskForCard, Damage, FinalizeStage

10 from thb.actions import GenericAction, LaunchCard, ShowCards, UserAction, migrate_cards, ttags

11 from thb.cards.base import CardList, Skill, VirtualCard

12 from thb.cards.classes import AttackCard, DollControlCard, DuelCard, TreatAs, t_None

13 from thb.characters.base import Character, register_character_to

14 from thb.inputlets import ChooseOptionInputlet

15 from thb.mode import THBEventHandler

16

17

18# -- code --

19 class 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

37 class 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

45 class 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 ↛ exit line 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

87 class Disarm(Skill):

88 associated_action = None

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

90 target = t_None()

91

92

93 class 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

107 class 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

131 class SentryAttack(TreatAs, VirtualCard):

132 treat_as = AttackCard

133

134

135 class Sentry(Skill):

136 associated_action = None

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

138 target = t_None()

139

140

141 class 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

172 class Telegnosis(Skill):

173 associated_action = None

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

175 target = t_None()

176

177

178 class 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 ↛ exit line 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

225 class 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

247 class 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')

255 class 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