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 

3from typing import Sequence, Tuple, List 

4# -- stdlib -- 

5# -- third party -- 

6# -- own -- 

7from thb.actions import ActionStage, Damage, DrawCards, DropCardStage, DropCards, GenericAction 

8from thb.actions import LaunchCard, PlayerTurn, PostCardMigrationHandler, Reforge, UserAction, MigrateCardsTransaction 

9from thb.actions import random_choose_card, user_choose_cards, user_choose_players 

10from thb.cards.base import Skill, VirtualCard 

11from thb.cards.definition import EquipmentCard 

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

13from thb.characters.base import Character, register_character_to 

14from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet 

15from thb.mode import THBEventHandler 

16 

17 

18# -- code -- 

19class LittleLegionAttackCard(TreatAs, VirtualCard): 

20 treat_as = AttackCard 

21 

22 

23class LittleLegionDollControlCard(TreatAs, VirtualCard): 

24 treat_as = DollControlCard 

25 

26 

27class LittleLegion(Skill): 

28 associated_action = None 

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

30 target = t_None() 

31 

32 

33class LittleLegionAttackAction(UserAction): 

34 def apply_action(self): 

35 g = self.game 

36 src = self.source 

37 pl = [p for p in g.players if not p.dead and p is not src] 

38 if not pl: 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true

39 return False 

40 

41 victim, = user_choose_players(self, src, pl) or (None,) 

42 if victim is None: 

43 return False 

44 

45 lc = LaunchCard(src, [victim], LittleLegionAttackCard(src), bypass_check=True) 

46 g.process_action(lc) 

47 return True 

48 

49 def choose_player_target(self, tl): 

50 if not tl: 50 ↛ 51line 50 didn't jump to line 51, because the condition on line 50 was never true

51 return (tl, False) 

52 

53 return (tl[-1:], True) 

54 

55 

56class LittleLegionCoverEffect(Heal): 

57 pass 

58 

59 

60class LittleLegionCoverAction(UserAction): 

61 def apply_action(self): 

62 g = self.game 

63 src = self.source 

64 pl = [p for p in g.players if not p.dead and p.life < p.maxlife] 

65 if not pl: 

66 return False 

67 

68 beneficiary, = user_choose_players(self, src, pl) or (None,) 

69 if beneficiary is None: 

70 return False 

71 

72 g.process_action(LittleLegionCoverEffect(src, beneficiary, 1)) 

73 return True 

74 

75 def choose_player_target(self, tl): 

76 if not tl: 76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true

77 return (tl, False) 

78 

79 return (tl[-1:], True) 

80 

81 

82class LittleLegionHoldAction(UserAction): 

83 def apply_action(self): 

84 src = self.source 

85 

86 g = self.game 

87 g.process_action(DrawCards(src, 1)) 

88 

89 turn = PlayerTurn.get_current(g) 

90 try: 

91 turn.pending_stages.remove(DropCardStage) 

92 except Exception: 

93 pass 

94 

95 return True 

96 

97 

98class LittleLegionControlAction(UserAction): 

99 def apply_action(self) -> bool: 

100 g = self.game 

101 src = self.source 

102 pl = [p for p in g.players if not p.dead] 

103 rst = user_choose_players(self, src, pl) 

104 

105 if not rst: 

106 return False 

107 

108 attacker, victim = rst 

109 self.target_list = attacker, victim 

110 

111 g.process_action(LaunchCard(src, [attacker, victim], LittleLegionDollControlCard(attacker))) 

112 return True 

113 

114 def choose_player_target(self, tl: Sequence[Character]) -> Tuple[List[Character], bool]: 

115 src = self.source 

116 trimmed, rst = DollControlCard.target(None, src, tl) 

117 return trimmed, rst and LaunchCard(src, trimmed, LittleLegionDollControlCard(src)).can_fire() 

118 

119 

120class LittleLegionHandler(THBEventHandler): 

121 interested = ['action_after'] 

122 

123 def handle(self, evt_type, act): 

124 if evt_type == 'action_after' and isinstance(act, ActionStage): 

125 tgt = act.target 

126 if not tgt.has_skill(LittleLegion): 

127 return act 

128 

129 c, = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips')) or (None,) 

130 if c is None: 

131 return act 

132 

133 g = self.game 

134 

135 assert isinstance(c, EquipmentCard) 

136 category = c.equipment_category 

137 

138 g.process_action(Reforge(tgt, tgt, c)) 

139 

140 if tgt.dead: 140 ↛ 141line 140 didn't jump to line 141, because the condition on line 140 was never true

141 return act 

142 

143 if category == 'weapon': 

144 g.process_action(LittleLegionAttackAction(tgt, tgt)) 

145 elif category == 'shield': 

146 g.process_action(LittleLegionCoverAction(tgt, tgt)) 

147 elif category == 'accessories': 

148 g.process_action(LittleLegionHoldAction(tgt, tgt)) 

149 elif category in ('redufo', 'greenufo'): 149 ↛ 152line 149 didn't jump to line 152, because the condition on line 149 was never false

150 g.process_action(LittleLegionControlAction(tgt, tgt)) 

151 

152 return act 

153 

154 def cond(self, cl): 

155 if len(cl) != 1: 

156 return False 

157 

158 c, = cl 

159 if c.is_card(VirtualCard): 

160 return False 

161 

162 return 'equipment' in c.category 

163 

164 

165class DollBlast(Skill): 

166 associated_action = None 

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

168 target = t_None() 

169 

170 

171class DollBlastEffect(GenericAction): 

172 def __init__(self, source, target, card, do_damage): 

173 self.source = source 

174 self.target = target 

175 self.card = card 

176 self.do_damage = do_damage 

177 

178 def apply_action(self): 

179 g = self.game 

180 src, tgt = self.source, self.target 

181 g.process_action(DropCards(src, tgt, [self.card])) 

182 if self.do_damage: 

183 g.process_action(Damage(src, tgt, 1)) 

184 

185 return True 

186 

187 

188class DollBlastAction(UserAction): 

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

190 self.source = source 

191 self.target = target 

192 self.cards = cards 

193 

194 def apply_action(self): 

195 g = self.game 

196 cl = self.cards 

197 track_ids = {c.track_id for c in cl} 

198 

199 src, tgt = self.source, self.target 

200 for c in cl: 

201 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards', 'equips'))) 

202 c = c or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips]) 

203 if not c: return True 203 ↛ exitline 203 didn't return from function 'apply_action', because the return on line 203 wasn't executed

204 g.players.reveal(c) 

205 g.process_action(DollBlastEffect(src, tgt, c, c.track_id in track_ids)) 

206 

207 return True 

208 

209 

210class DollBlastHandlerCommon(object): 

211 

212 def fire(self, src, tgt, cards): 

213 self.target = tgt # for ui 

214 

215 g = self.game 

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

217 return 

218 

219 g.process_action(DollBlastAction(src, tgt, cards)) 

220 

221 

222class DollBlastMigrationHandler(DollBlastHandlerCommon, THBEventHandler): 

223 interested = ['post_card_migration'] 

224 arbiter = PostCardMigrationHandler 

225 

226 def handle(self, p, trans): 

227 if not p.has_skill(DollBlast) or p.dead: 

228 return True 

229 

230 equips = p.equips 

231 

232 assert isinstance(trans, MigrateCardsTransaction) 

233 for m in trans.movements: 

234 if m.fr is not equips: 

235 continue 

236 

237 if not m.to.owner: 

238 continue 

239 

240 tgt = m.to.owner 

241 

242 if tgt is m.fr.owner: 

243 continue 

244 

245 if not (tgt.cards or tgt.showncards or tgt.equips): 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true

246 continue 

247 

248 self.fire(p, tgt, [m.card]) 

249 

250 return True 

251 

252 

253class DollBlastDropHandler(DollBlastHandlerCommon, THBEventHandler): 

254 interested = ['action_before', 'action_after'] 

255 

256 def handle(self, evt_type, act): 

257 if evt_type == 'action_before' and isinstance(act, DropCards): 

258 src, tgt = act.source, act.target 

259 

260 if not tgt.has_skill(DollBlast): 

261 return act 

262 

263 if not src or src is tgt or not (src.cards or src.showncards or src.equips): 

264 return act 

265 

266 if not any(c.resides_in.type == 'equips' for c in act.cards): 

267 return act 

268 

269 act._['doll_blast'] = True 

270 

271 elif evt_type == 'action_after' and isinstance(act, DropCards) and act._['doll_blast']: 

272 tgt = act.target 

273 if tgt.has_skill(DollBlast): 273 ↛ 276line 273 didn't jump to line 276, because the condition on line 273 was never false

274 self.fire(act.target, act.source, act.cards) 

275 

276 return act 

277 

278 

279@register_character_to('common') 

280class Alice(Character): 

281 skills = [DollBlast, LittleLegion] 

282 eventhandlers = [ 

283 DollBlastMigrationHandler, 

284 DollBlastDropHandler, 

285 LittleLegionHandler, 

286 ] 

287 maxlife = 4