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

5from typing import cast 

6 

7# -- third party -- 

8# -- own -- 

9from game.base import EventHandler 

10from thb.actions import ActionStage, ActionStageLaunchCard, AskForCard, Damage, DropCards, ForEach 

11from thb.actions import GenericAction, LaunchCard, MigrateCardsTransaction, PrepareStage, UseCard 

12from thb.actions import UserAction, VitalityLimitExceeded, register_eh, user_choose_cards 

13 

14 

15# -- code -- 

16class BasicAction(UserAction): 

17 pass 

18 

19 

20class BaseAttack(UserAction): 

21 

22 def __init__(self, source, target, damage=1): 

23 self.source = source 

24 self.target = target 

25 self.damage = damage 

26 

27 def apply_action(self): 

28 g = self.game 

29 source, target = self.source, self.target 

30 rst = g.process_action(LaunchGraze(target)) 

31 self1, rst = g.emit_event('attack_aftergraze', (self, not rst)) 

32 assert self1 is self 

33 assert rst in (False, True) 

34 if rst: 

35 g.process_action(Damage(source, target, amount=self.damage)) 

36 return True 

37 else: 

38 return False 

39 

40 def is_valid(self): 

41 return not self.target.dead 

42 

43 

44class Attack(BaseAttack, BasicAction): 

45 pass 

46 

47 

48class InevitableAttack(Attack): 

49 def apply_action(self): 

50 g = self.game 

51 dmg = Damage(self.source, self.target, amount=self.damage) 

52 g.process_action(dmg) 

53 return True 

54 

55 

56@register_eh 

57class AttackCardRangeHandler(EventHandler): 

58 interested = ['calcdistance'] 

59 

60 def handle(self, evt_type, act): 

61 if evt_type == 'calcdistance': 61 ↛ 67line 61 didn't jump to line 67, because the condition on line 61 was never false

62 src, card, dist = act 

63 from .definition import AttackCard 

64 if card.is_card(AttackCard): 

65 self.fix_attack_range(src, dist) 

66 

67 return act 

68 

69 @staticmethod 

70 def attack_range_bonus(p): 

71 from .equipment import WeaponSkill 

72 l = [ 

73 s.range - 1 for s in p.skills 

74 if issubclass(s, WeaponSkill) and p.has_skill(s) 

75 ] 

76 return max(0, 0, *l) 

77 

78 @classmethod 

79 def fix_attack_range(cls, src, dist): 

80 l = cls.attack_range_bonus(src) 

81 for p in dist: 

82 dist[p] -= l 

83 

84 

85@register_eh 

86class AttackCardVitalityHandler(EventHandler): 

87 interested = ['action_before', 'action_shootdown'] 

88 

89 @classmethod 

90 def handle(cls, evt_type, act): 

91 if evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard): 

92 from .definition import AttackCard 

93 src = act.source 

94 if act.card.is_card(AttackCard) and not cls.is_disabled(src): 

95 act._[cls] = 'already-handled' 

96 src.tags['vitality'] -= 1 

97 

98 elif evt_type == 'action_shootdown' and isinstance(act, ActionStageLaunchCard): 

99 from .definition import AttackCard 

100 if act.card.is_card(AttackCard): 

101 src = act.source 

102 if cls.is_disabled(src): 

103 return act 

104 

105 if act._[cls]: 

106 return act 

107 

108 if src.tags['vitality'] > 0: 

109 return act 

110 

111 raise VitalityLimitExceeded 

112 

113 return act 

114 

115 @staticmethod 

116 def disable(p): 

117 p.tags['attack_card_vitality'] = p.tags['turn_count'] 

118 

119 @staticmethod 

120 def enable(p): 

121 p.tags['attack_card_vitality'] = 0 

122 

123 @staticmethod 

124 def is_disabled(p): 

125 return p.tags['attack_card_vitality'] >= p.tags['turn_count'] 

126 

127 

128@register_eh 

129class VitalityHandler(EventHandler): 

130 interested = ['action_before'] 

131 

132 def handle(self, evt_type, act): 

133 if evt_type == 'action_before' and isinstance(act, ActionStage): 

134 act.source.tags['vitality'] = 1 

135 

136 return act 

137 

138 

139class Heal(BasicAction): 

140 

141 def __init__(self, source, target, amount=1): 

142 self.source = source 

143 self.target = target 

144 self.amount = amount 

145 

146 def apply_action(self): 

147 target = self.target 

148 if target.life < target.maxlife: 148 ↛ 152line 148 didn't jump to line 152, because the condition on line 148 was never false

149 target.life = min(target.life + self.amount, target.maxlife) 

150 return True 

151 else: 

152 return False 

153 

154 def is_valid(self): 

155 tgt = self.target 

156 return not tgt.dead and tgt.life < tgt.maxlife 

157 

158 

159class GrazeAction(BasicAction): 

160 

161 def apply_action(self): 

162 return True 

163 

164 

165class UseAttack(AskForCard): 

166 card_usage = 'use' 

167 

168 def __init__(self, target): 

169 from thb.cards.definition import AttackCard 

170 AskForCard.__init__(self, target, target, AttackCard) 

171 

172 def process_card(self, card): 

173 g = self.game 

174 return g.process_action(UseCard(self.target, card)) 

175 

176 def ask_for_action_verify(self, p, cl, tl): 

177 return UseCard(p, cl[0]).can_fire() 

178 

179 

180class BaseUseGraze(AskForCard): 

181 def __init__(self, target): 

182 from thb.cards.definition import GrazeCard 

183 AskForCard.__init__(self, target, target, GrazeCard) 

184 

185 

186class UseGraze(BaseUseGraze): 

187 card_usage = 'use' 

188 

189 def process_card(self, card): 

190 g = self.game 

191 return g.process_action(UseCard(self.target, card)) 

192 

193 def ask_for_action_verify(self, p, cl, tl): 

194 return UseCard(p, cl[0]).can_fire() 

195 

196 

197class LaunchGraze(BaseUseGraze): 

198 card_usage = 'launch' 

199 

200 def process_card(self, card): 

201 g = self.game 

202 tgt = self.target 

203 return g.process_action(LaunchCard(tgt, [tgt], card, GrazeAction(tgt, tgt))) 

204 

205 def ask_for_action_verify(self, p, cl, tl): 

206 tgt = self.target 

207 return LaunchCard(tgt, [tgt], cl[0], GrazeAction(tgt, tgt)).can_fire() 

208 

209 

210class AskForHeal(AskForCard): 

211 card_usage = 'launch' 

212 

213 def __init__(self, source, target): 

214 from thb import cards 

215 AskForCard.__init__(self, source, target, cards.definition.HealCard) 

216 

217 def process_card(self, card): 

218 g = self.game 

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

220 heal_cls = card.associated_action or Heal 

221 return g.process_action(LaunchCard(tgt, [src], card, heal_cls(tgt, src))) 

222 

223 def ask_for_action_verify(self, p, cl, tl): 

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

225 card = cl[0] 

226 heal_cls = card.associated_action or Heal 

227 return LaunchCard(tgt, [src], card, heal_cls(tgt, src)).can_fire() 

228 

229 

230class Wine(BasicAction): 

231 def apply_action(self): 

232 self.target.tags['wine'] = True 

233 return True 

234 

235 def is_valid(self): 

236 return not self.target.dead 

237 

238 

239class SoberUp(GenericAction): 

240 def apply_action(self): 

241 self.target.tags['wine'] = False 

242 return True 

243 

244 

245class WineRevive(GenericAction): 

246 def __init__(self, act): 

247 self.act = act 

248 self.source = act.target 

249 self.target = act.target 

250 

251 def apply_action(self): 

252 self.act.amount -= 1 

253 tgt = self.target 

254 self.game.process_action(SoberUp(tgt, tgt)) 

255 return True 

256 

257 

258@register_eh 

259class WineHandler(EventHandler): 

260 interested = ['action_apply', 'action_before', 'post_choose_target'] 

261 

262 def handle(self, evt_type, act): 

263 if evt_type == 'action_before' and isinstance(act, BaseAttack): 

264 pact = ForEach.get_actual_action(act) or act 

265 if getattr(pact, 'in_wine', False): 

266 act.damage += 1 

267 

268 elif evt_type == 'post_choose_target': 

269 act, tl = arg = act 

270 

271 from thb.cards.definition import AttackCard 

272 if act.card.is_card(AttackCard): 

273 src = act.source 

274 if src.tags['wine']: 

275 self.game.process_action(SoberUp(src, src)) 

276 act.card_action.in_wine = True 

277 

278 return arg 

279 

280 elif evt_type == 'action_apply' and isinstance(act, PrepareStage): 

281 src = act.target 

282 if src.tags['wine']: 

283 self.game.process_action(SoberUp(src, src)) 

284 

285 elif evt_type == 'action_before' and isinstance(act, Damage): 

286 if act.cancelled: return act 286 ↛ exitline 286 didn't return from function 'handle', because the return on line 286 wasn't executed

287 if act.amount < 1: return act 

288 tgt = act.target 

289 if act.amount >= tgt.life and tgt.tags['wine']: 

290 g = self.game 

291 g.process_action(WineRevive(act)) 

292 

293 return act 

294 

295 

296class Exinwan(BasicAction): 

297 # 恶心丸 

298 def apply_action(self): 

299 return True 

300 

301 

302class ExinwanEffect(GenericAction): 

303 # 恶心丸 

304 card_usage = 'drop' 

305 

306 def apply_action(self): 

307 g = self.game 

308 tgt = self.target 

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

310 return False 

311 

312 cards = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips')) 

313 

314 if cards: 314 ↛ 315line 314 didn't jump to line 315, because the condition on line 314 was never true

315 g.process_action(DropCards(tgt, tgt, cards)) 

316 else: 

317 g.process_action(Damage(None, tgt)) 

318 

319 return True 

320 

321 def cond(self, cards): 

322 if len(cards) != 2: return False 322 ↛ 323line 322 didn't jump to line 323, because the condition on line 322 was never false

323 from thb.cards.base import Skill 

324 if any(isinstance(c, Skill) for c in cards): return False 

325 return True 

326 

327 def is_valid(self): 

328 return not self.target.dead 

329 

330 

331@register_eh 

332class ExinwanHandler(EventHandler): 

333 # 恶心丸 

334 

335 interested = ['post_card_migration'] 

336 

337 def handle(self, evt_type, arg) -> None: 

338 from thb.cards.base import VirtualCard, HiddenCard 

339 from thb.cards.definition import ExinwanCard 

340 

341 if evt_type == 'post_card_migration': 341 ↛ 363line 341 didn't jump to line 363, because the condition on line 341 was never false

342 moves = cast(MigrateCardsTransaction, arg).movements 

343 moves = [m for m in moves if m.to.type == 'droppedcard'] 

344 

345 # cards to dropped area should all unwrapped 

346 assert not any( 

347 m.card.is_card(VirtualCard) or m.card.is_card(HiddenCard) 

348 for m in moves 

349 ) 

350 

351 moves = [m for m in moves if m.card.is_card(ExinwanCard)] 

352 

353 # no same card dropped twice in the same transaction 

354 assert len(moves) == len(set(m.card for m in moves)) 

355 

356 for m in moves: 

357 tgt = m.trans.action.source 

358 if tgt and not tgt.dead: 

359 act = ExinwanEffect(tgt, tgt) 

360 act.associated_card = m.card 

361 self.game.process_action(act) 

362 

363 return arg