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 List, cast 

6 

7# -- third party -- 

8# -- own -- 

9from game.base import EventHandler, GameError 

10from thb.actions import ActionLimitExceeded, ActionStageLaunchCard, Damage, DrawCards, DropCardStage 

11from thb.actions import DropCards, FatetellAction, FatetellStage, FinalizeStage, ForEach 

12from thb.actions import GenericAction, LaunchCard, MaxLifeChange, MigrateCardsTransaction, Reforge 

13from thb.actions import UserAction, VitalityLimitExceeded, detach_cards, migrate_cards 

14from thb.actions import random_choose_card, register_eh, ttags, user_choose_cards 

15from thb.cards import basic, spellcard 

16from thb.cards.base import Card, PhysicalCard, Skill, TreatAs, VirtualCard, t_None 

17from thb.cards.base import t_OtherLessEqThanN, t_OtherOne 

18from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet 

19from utils.check import CheckFailed, check 

20from utils.misc import classmix 

21 

22 

23# -- code -- 

24class WearEquipmentAction(UserAction): 

25 def apply_action(self): 

26 g = self.game 

27 card = self.associated_card 

28 tgt = self.target 

29 equips = tgt.equips 

30 

31 _s, _t, _c, rst = g.emit_event('wear_equipment', (self, tgt, card, 'default')) 

32 assert _s is self 

33 assert _t is tgt 

34 assert _c is card 

35 assert rst in ('default', 'handled') 

36 

37 if rst == 'handled': 37 ↛ 38line 37 didn't jump to line 38, because the condition on line 37 was never true

38 return True 

39 

40 for oc in list(equips): 

41 if oc.equipment_category == card.equipment_category: 

42 g.process_action(DropCards(tgt, tgt, [oc])) 

43 

44 migrate_cards([card], tgt.equips) 

45 

46 return True 

47 

48 

49class ReforgeWeapon(Reforge): 

50 pass 

51 

52 

53@register_eh 

54class WeaponReforgeHandler(EventHandler): 

55 interested = ['action_before'] 

56 

57 def handle(self, evt_type, act): 

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

59 c = act.card 

60 tgt = act.target 

61 

62 if c.is_card(VirtualCard): return act 

63 from thb.cards.definition import EquipmentCard 

64 if not isinstance(c, EquipmentCard): return act 

65 if c.equipment_category != 'weapon': return act 

66 if tgt.tags['vitality'] <= 0: return act 

67 

68 g = self.game 

69 

70 if g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

71 tgt.tags['vitality'] -= 1 

72 g.process_action(ReforgeWeapon(tgt, tgt, c)) 

73 act.cancelled = True 

74 

75 return act 

76 

77 

78@register_eh 

79class EquipmentTransferHandler(EventHandler): 

80 interested = ['post_card_migration'] 

81 

82 def handle(self, evt, trans): 

83 if evt == 'post_card_migration': 83 ↛ 96line 83 didn't jump to line 96, because the condition on line 83 was never false

84 assert isinstance(trans, MigrateCardsTransaction) 

85 for m in trans.movements: 

86 if m.fr.type == 'equips': 

87 try: 

88 m.fr.owner.skills.remove(m.card.equipment_skill) 

89 except ValueError: 

90 pass 

91 

92 if m.to.type == 'equips': 

93 if m.card.equipment_skill: 93 ↛ 85line 93 didn't jump to line 85, because the condition on line 93 was never false

94 m.to.owner.skills.append(m.card.equipment_skill) 

95 

96 return trans 

97 

98 

99class ShieldSkill(Skill): 

100 associated_action = None 

101 target = t_None() 

102 

103 

104class OpticalCloakSkill(TreatAs, ShieldSkill): # just a tag 

105 treat_as = PhysicalCard.classes['GrazeCard'] 

106 skill_category = ['equip', 'passive'] 

107 

108 def check(self): 

109 return False 

110 

111 

112class OpticalCloak(FatetellAction): 

113 # 光学迷彩 

114 def __init__(self, source, target): 

115 self.source = source 

116 self.target = target 

117 self.fatetell_target = target 

118 self.fatetell_cond = lambda card: card.color == Card.RED 

119 

120 def fatetell_action(self, ft): 

121 self.fatetell_card = ft.card 

122 return bool(ft.succeeded) 

123 

124 

125@register_eh 

126class OpticalCloakHandler(EventHandler): 

127 interested = ['action_apply'] 

128 execute_after = ['AssistedGrazeHandler'] 

129 

130 def handle(self, evt_type, act): 

131 from .basic import BaseUseGraze 

132 if evt_type == 'action_apply' and isinstance(act, BaseUseGraze): 

133 tgt = act.target 

134 if not tgt.has_skill(OpticalCloakSkill): return act 

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

136 

137 g = self.game 

138 

139 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

140 return act 

141 

142 if g.process_action(OpticalCloak(tgt, tgt)): 

143 act.card = OpticalCloakSkill(tgt) 

144 

145 return act 

146 

147 

148class MomijiShieldSkill(ShieldSkill): 

149 skill_category = ['equip', 'passive'] 

150 

151 

152class MomijiShield(GenericAction): 

153 def __init__(self, act): 

154 self.action = act 

155 self.source = self.target = act.target 

156 

157 def apply_action(self): 

158 self.action.cancelled = True 

159 return True 

160 

161 

162@register_eh 

163class MomijiShieldHandler(EventHandler): 

164 interested = ['action_before'] 

165 execute_before = ['HouraiJewelHandler'] 

166 

167 def handle(self, evt_type, act): 

168 from .basic import BaseAttack 

169 if not (evt_type == 'action_before' and isinstance(act, BaseAttack)): return act 

170 tgt = act.target 

171 if not tgt.has_skill(MomijiShieldSkill): return act 

172 if not act.associated_card.color == Card.BLACK: return act 

173 g = self.game 

174 g.process_action(MomijiShield(act)) 

175 

176 return act 

177 

178 

179class UFOSkill(Skill): 

180 associated_action = None 

181 skill_category = ['equip', 'passive'] 

182 target = t_None() 

183 

184 

185class GreenUFOSkill(UFOSkill): 

186 increment = 1 

187 

188 

189class RedUFOSkill(UFOSkill): 

190 increment = 1 

191 

192 

193@register_eh 

194class UFODistanceHandler(EventHandler): 

195 interested = ['calcdistance'] 

196 

197 def handle(self, evt_type, arg): 

198 if not evt_type == 'calcdistance': return arg 198 ↛ exitline 198 didn't return from function 'handle', because the return on line 198 wasn't executed

199 

200 src, card, dist = arg 

201 for s in src.skills: 

202 if not issubclass(s, RedUFOSkill): continue 

203 if not src.has_skill(s): continue 203 ↛ 201line 203 didn't jump to line 201, because the continue on line 203 wasn't executed

204 incr = s.increment 

205 incr = incr(src) if callable(incr) else incr 

206 for p in dist: 

207 dist[p] -= incr 

208 

209 for p in dist: 

210 for s in p.skills: 

211 if not issubclass(s, GreenUFOSkill): continue 

212 if not p.has_skill(s): continue 212 ↛ 210line 212 didn't jump to line 210, because the continue on line 212 wasn't executed

213 incr = s.increment 

214 dist[p] += incr(p) if callable(incr) else incr 

215 

216 return arg 

217 

218 

219class WeaponSkill(Skill): 

220 range = 1 

221 

222 

223class RoukankenSkill(WeaponSkill): 

224 associated_action = None 

225 skill_category = ['equip', 'passive'] 

226 target = t_None() 

227 range = 3 

228 

229 

230class Roukanken(GenericAction): 

231 def __init__(self, act): 

232 assert isinstance(act, basic.BaseAttack) 

233 self.action = act 

234 self.source = act.source 

235 self.target = act.target 

236 

237 def apply_action(self): 

238 act = self.action 

239 tgt = act.target 

240 

241 skills = [s for s in tgt.skills if issubclass(s, ShieldSkill)] 

242 for s in skills: 

243 tgt.disable_skill(s, 'roukanken') 

244 

245 act.roukanken_disabled_skills = skills 

246 return self.game.process_action(act) 

247 

248 

249@register_eh 

250class RoukankenEffectHandler(EventHandler): 

251 interested = ('action_before', 'action_done',) 

252 execute_before = ( 

253 'MomijiShieldHandler', 

254 'OpticalCloakHandler', 

255 'SaigyouBranchHandler', 

256 'HouraiJewelHandler', 

257 'SpearTheGungnirHandler', 

258 'HakuroukenHandler', 

259 'FreakingPowerHandler', 

260 'ResonanceHandler' 

261 ) 

262 

263 @classmethod 

264 def handle(cls, evt_type, act): 

265 if evt_type == 'action_before' and isinstance(act, basic.BaseAttack): 

266 if act.cancelled: 266 ↛ 267line 266 didn't jump to line 267, because the condition on line 266 was never true

267 return act 

268 

269 if act._[cls]: 

270 return act 

271 

272 act._[cls] = 'roukanken-processed' 

273 

274 source = act.source 

275 if source.has_skill(RoukankenSkill): 

276 act = Roukanken(act) 

277 return act 

278 

279 if evt_type == 'action_done' and isinstance(act, basic.BaseAttack): 

280 if hasattr(act, 'roukanken_tag'): 280 ↛ 281line 280 didn't jump to line 281, because the condition on line 280 was never true

281 for s in getattr(act, 'roukanken_disabled_skills', []): 

282 act.target.reenable_skill('roukanken') 

283 

284 return act 

285 

286 

287class NenshaPhoneSkill(WeaponSkill): 

288 associated_action = None 

289 skill_category = ['equip', 'passive'] 

290 target = t_None() 

291 range = 4 

292 

293 

294class NenshaPhone(GenericAction): 

295 def apply_action(self): 

296 tgt = self.target 

297 

298 cards = list(tgt.cards)[:2] 

299 g = self.game 

300 g.players.exclude(tgt).reveal(cards) 

301 migrate_cards(cards, tgt.showncards) 

302 

303 return True 

304 

305 

306@register_eh 

307class NenshaPhoneHandler(EventHandler): 

308 interested = ['action_after'] 

309 

310 def handle(self, evt_type, act): 

311 if evt_type == 'action_after' and isinstance(act, Damage): 

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

313 g = self.game 

314 pa = g.action_stack[-1] 

315 if not isinstance(pa, basic.BaseAttack): return act 

316 

317 src = act.source 

318 tgt = act.target 

319 if tgt.dead: return act 

320 if not tgt.cards: return act 

321 if not src.has_skill(NenshaPhoneSkill): return act 

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

323 g.process_action(NenshaPhone(src, tgt)) 

324 

325 return act 

326 

327 

328class ElementalReactorSkill(WeaponSkill): 

329 associated_action = None 

330 skill_category = ['equip', 'passive'] 

331 target = t_None() 

332 range = 1 

333 

334 

335@register_eh 

336class ElementalReactorHandler(EventHandler): 

337 interested = ['action_stage_action', 'post_card_migration'] 

338 execute_after = ['EquipmentTransferHandler'] 

339 

340 def handle(self, evt_type, arg): 

341 if evt_type == 'action_stage_action': 

342 tgt = arg 

343 if not tgt.has_skill(ElementalReactorSkill): return arg 

344 basic.AttackCardVitalityHandler.disable(tgt) 

345 

346 elif evt_type == 'post_card_migration': 346 ↛ 354line 346 didn't jump to line 354, because the condition on line 346 was never false

347 trans = cast(MigrateCardsTransaction, arg) 

348 from .definition import ElementalReactorCard 

349 

350 for m in trans.movements: 

351 if m.fr.type == 'equips' and m.card.is_card(ElementalReactorCard): 

352 basic.AttackCardVitalityHandler.enable(m.fr.owner) 

353 

354 return arg 

355 

356 

357class GungnirSkill(TreatAs, WeaponSkill): 

358 target = t_OtherOne() 

359 skill_category = ['equip', 'active'] 

360 range = 3 

361 treat_as = PhysicalCard.classes['AttackCard'] # arghhhhh, nasty circular references! 

362 

363 def check(self): 

364 cl = self.associated_cards 

365 cat = ('cards', 'showncards') 

366 if not all(c.resides_in.type in cat for c in cl): return False 

367 if not all(c.is_card(PhysicalCard) for c in cl): return False 367 ↛ exitline 367 didn't return from function 'check', because the return on line 367 wasn't executed

368 return len(cl) == 2 

369 

370 

371class ScarletRhapsody(ForEach): 

372 action_cls = basic.Attack 

373 

374 

375class ScarletRhapsodySkill(WeaponSkill): 

376 range = 4 

377 associated_action = ScarletRhapsody 

378 category = ['skill', 'treat_as', 'basic'] 

379 skill_category = ['equip', 'active'] 

380 target = t_OtherLessEqThanN(3) 

381 usage = 'launch' 

382 

383 def check(self): 

384 try: 

385 cl = self.associated_cards 

386 check(len(cl) == 1) 

387 card = cl[0] 

388 from .definition import AttackCard 

389 check(card.is_card(AttackCard)) 

390 tgt = card.resides_in.owner 

391 check(card.is_card(PhysicalCard)) 

392 

393 check(card.resides_in in (tgt.cards, tgt.showncards)) 

394 check(card in set(tgt.cards) or card in set(tgt.showncards)) 

395 

396 check(set(tgt.cards) | set(tgt.showncards) == set([card])) 

397 

398 return True 

399 except CheckFailed: 

400 return False 

401 

402 def is_card(self, cls): 

403 from thb.cards.definition import AttackCard 

404 if issubclass(AttackCard, cls): return True 

405 return isinstance(self, cls) 

406 

407 @property 

408 def distance(self): 

409 try: 

410 return max(1, self.associated_cards[0].distance) 

411 except Exception: 

412 return 1 

413 

414 

415class RepentanceStickSkill(WeaponSkill): 

416 range = 2 

417 skill_category = ['equip', 'passive'] 

418 associated_action = None 

419 target = t_None() 

420 

421 

422class RepentanceStick(GenericAction): 

423 def apply_action(self) -> bool: 

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

425 g = self.game 

426 

427 catnames = ('cards', 'showncards', 'equips', 'fatetell') 

428 cats = [getattr(tgt, i) for i in catnames] 

429 

430 l: List[PhysicalCard] = [] 

431 for i in range(2): 

432 if not (tgt.cards or tgt.showncards or tgt.equips or tgt.fatetell): 

433 break 

434 

435 card = g.user_input( 

436 [src], ChoosePeerCardInputlet(self, tgt, catnames) 

437 ) 

438 

439 if not card: 

440 card = random_choose_card(g, cats) 

441 if card: 441 ↛ 431line 441 didn't jump to line 431, because the condition on line 441 was never false

442 l.append(card) 

443 g.players.exclude(tgt).player.reveal(card) 

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

445 

446 self.cards = l 

447 return True 

448 

449 

450@register_eh 

451class RepentanceStickHandler(EventHandler): 

452 interested = ['action_before'] 

453 execute_before = ['WineHandler'] 

454 

455 def handle(self, evt_type, act): 

456 if evt_type == 'action_before' and isinstance(act, Damage): 

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

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

459 if src and src.has_skill(RepentanceStickSkill): 

460 g = self.game 

461 pa = g.action_stack[-1] 

462 if not isinstance(pa, basic.BaseAttack): return act 

463 if not (tgt.cards or tgt.showncards or tgt.equips or tgt.fatetell): 

464 return act 

465 

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

467 return act 

468 

469 g.process_action(RepentanceStick(src, tgt)) 

470 act.cancelled = True 

471 

472 return act 

473 

474 

475class IbukiGourdSkill(RedUFOSkill): 

476 skill_category = ['equip', 'passive'] 

477 increment = 0 

478 

479 

480@register_eh 

481class IbukiGourdHandler(EventHandler): 

482 interested = ('action_apply', 'action_after', 'card_migration') 

483 

484 def handle(self, evt_type, act): 

485 if evt_type == 'action_after' and isinstance(act, Damage): 

486 src = act.source 

487 if not src: return act 

488 if not src.has_skill(IbukiGourdSkill): return act 

489 

490 g = self.game 

491 ttags(src)['ibukigourd_did_damage'] = True 

492 

493 elif evt_type == 'action_apply' and isinstance(act, FinalizeStage): 

494 tgt = act.target 

495 if not tgt.has_skill(IbukiGourdSkill): return act 

496 

497 g = self.game 

498 if ttags(tgt)['ibukigourd_did_damage']: return act 

499 

500 g.process_action(basic.Wine(tgt, tgt)) 

501 

502 elif evt_type == 'post_card_migration': 502 ↛ 503line 502 didn't jump to line 503, because the condition on line 502 was never true

503 from .definition import IbukiGourdCard 

504 trans = cast(MigrateCardsTransaction, act) 

505 

506 for m in trans.movements: 

507 if m.card.is_card(IbukiGourdCard) and m.to.type == 'equips': 

508 tgt = m.to.owner 

509 g = self.game 

510 g.process_action(basic.Wine(tgt, tgt)) 

511 

512 return act 

513 

514 

515class HouraiJewelAttack(basic.BaseAttack, spellcard.InstantSpellCardAction): 

516 def apply_action(self): 

517 g = self.game 

518 g.process_action(Damage(self.source, self.target)) 

519 return True 

520 

521 

522class HouraiJewelSkill(WeaponSkill): 

523 associated_action = None 

524 skill_category = ['equip', 'passive'] 

525 target = t_None() 

526 range = 1 

527 

528 

529@register_eh 

530class HouraiJewelHandler(EventHandler): 

531 interested = ['action_before'] 

532 execute_before = ['RejectHandler', 'WineHandler'] # wine does not affect this. 

533 

534 def handle(self, evt_type, act): 

535 if evt_type == 'action_before' and isinstance(act, basic.Attack): 

536 src = act.source 

537 if not src.has_skill(HouraiJewelSkill): return act 

538 if isinstance(act, HouraiJewelAttack): return act 538 ↛ exitline 538 didn't return from function 'handle', because the return on line 538 wasn't executed

539 g = self.game 

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

541 act.__class__ = classmix(HouraiJewelAttack, act.__class__) 

542 

543 return act 

544 

545 

546class UmbrellaSkill(ShieldSkill): 

547 skill_category = ['equip', 'passive'] 

548 

549 

550class UmbrellaEffect(GenericAction): 

551 def __init__(self, act, damage_act): 

552 self.source = self.target = damage_act.target 

553 self.action = act 

554 self.damage_act = damage_act 

555 

556 def apply_action(self): 

557 self.damage_act.cancelled = True 

558 return True 

559 

560 

561@register_eh 

562class UmbrellaHandler(EventHandler): 

563 # 紫的阳伞 

564 interested = ['action_before'] 

565 execute_before = ['RejectHandler'] 

566 

567 def handle(self, evt_type, act): 

568 if evt_type == 'action_before' and isinstance(act, Damage): 

569 if not act.target.has_skill(UmbrellaSkill): return act 

570 g = self.game 

571 pact = g.action_stack[-1] 

572 

573 if isinstance(pact, spellcard.SpellCardAction): 

574 self.game.process_action(UmbrellaEffect(pact, act)) 

575 

576 return act 

577 

578 

579class MaidenCostume(TreatAs, ShieldSkill): 

580 treat_as = PhysicalCard.classes['RejectCard'] 

581 skill_category = ['equip', 'passive'] 

582 

583 def check(self): 

584 return False 

585 

586 

587class MaidenCostumeAction(FatetellAction): 

588 def __init__(self, source, act): 

589 self.source = source 

590 self.target = source 

591 self.fatetell_target = source 

592 self.fatetell_cond = lambda c: 9 <= c.number <= 13 

593 self.act = act 

594 

595 def fatetell_action(self, ft): 

596 act = self.act 

597 src = self.source 

598 

599 g = self.game 

600 if ft.succeeded: 

601 # rej = spellcard.LaunchReject(src, act, SaigyouBranchSkill(src)) 

602 g.process_action(LaunchCard( 

603 src, [act.target], MaidenCostume(src), spellcard.Reject(src, act) 

604 )) 

605 return True 

606 else: 

607 return False 

608 

609 

610@register_eh 

611class MaidenCostumeHandler(EventHandler): 

612 interested = ['action_before'] 

613 execute_before = ['RejectHandler'] 

614 execute_after = ['HouraiJewelHandler'] 

615 

616 def handle(self, evt_type, act): 

617 if evt_type == 'action_before' and isinstance(act, spellcard.SpellCardAction): 

618 tgt = act.target 

619 if not tgt.has_skill(MaidenCostume): return act 

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

621 if isinstance(act, spellcard.Reject): return act # can't respond to reject 

622 g = self.game 

623 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

624 return act 

625 

626 self.game.process_action(MaidenCostumeAction(tgt, act)) 

627 

628 return act 

629 

630 

631class HakuroukenSkill(WeaponSkill): 

632 range = 2 

633 skill_category = ['equip', 'passive'] 

634 associated_action = None 

635 target = t_None() 

636 

637 

638class Hakurouken(GenericAction): 

639 card_usage = 'drop' 

640 

641 def apply_action(self): 

642 src = self.source 

643 tgt = self.target 

644 

645 cards = user_choose_cards(self, tgt, ('cards', 'showncards')) 

646 g = self.game 

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

648 self.peer_action = 'drop' 

649 g.process_action(DropCards(src, tgt, cards)) 

650 else: 

651 self.peer_action = 'draw' 

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

653 

654 return True 

655 

656 def cond(self, cards): 

657 return len(cards) == 1 and not cards[0].is_card(Skill) 

658 

659 

660@register_eh 

661class HakuroukenHandler(EventHandler): 

662 interested = ['action_before'] 

663 

664 # BUG WITH OUT THIS LINE: 

665 # src equips [Gourd, Hakurouken], tgt drops Exinwan 

666 # then src drops Gourd, 

667 # but Attack.damage == 1, Wine tag preserved. 

668 execute_before = ['WineHandler', 'MomijiShieldHandler'] 

669 

670 def handle(self, evt_type, act): 

671 if evt_type == 'action_before' and isinstance(act, basic.BaseAttack): 

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

673 src = act.source 

674 if not src.has_skill(HakuroukenSkill): return act 

675 card = act.associated_card 

676 if not card.suit == Card.CLUB: return act 

677 

678 g = self.game 

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

680 return act 

681 

682 g.process_action(Hakurouken(src, act.target)) 

683 

684 return act 

685 

686 

687class AyaRoundfan(GenericAction): 

688 def apply_action(self): 

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

690 g = self.game 

691 

692 equip = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ['equips'])) 

693 equip = equip or random_choose_card(g, [tgt.equips]) 

694 g.process_action(DropCards(src, tgt, [equip])) 

695 self.card = equip 

696 

697 return True 

698 

699 def is_valid(self): 

700 # Proton 2015/4/2 0:26:51 

701 # 小爱打幽香,打中以后,弃丸子发动团扇 

702 # Proton 2015/4/2 0:26:59 

703 # 然后弃了自己的装备 

704 # Proton 2015/4/2 0:27:25 

705 # 发动了于是又弃了幽香的装备 

706 # Proton 2015/4/2 0:27:41 

707 # 发动了小小军势 

708 # Proton 2015/4/2 0:28:02 

709 # 这时候针妙丸又来了一发,幽香卒 

710 # 0:28:19 

711 # 八咫乌鸦 2015/4/2 0:28:19 

712 # 喜闻乐见的插入结算 

713 # Proton 2015/4/2 0:28:49 

714 # 嗯 然后就回到了团扇的效果 

715 # Proton 2015/4/2 0:29:08 

716 # 团扇弃置cost把对面弃死了啊! 

717 # Proton 2015/4/2 0:29:38 

718 # 然后后半段效果都假设是对面还活着 

719 # Proton 2015/4/2 0:29:44 

720 # 崩 

721 

722 # 所以加上这个 

723 return self.target.equips 

724 

725 

726class AyaRoundfanSkill(WeaponSkill): 

727 range = 5 

728 skill_category = ['equip', 'passive'] 

729 associated_action = None 

730 target = t_None() 

731 

732 

733@register_eh 

734class AyaRoundfanHandler(EventHandler): 

735 interested = ('action_after',) 

736 execute_after = ('DyingHandler', ) 

737 card_usage = 'drop' 

738 

739 def handle(self, evt_type, act): 

740 if evt_type == 'action_after' and isinstance(act, Damage): 

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

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

743 if not (src and src.has_skill(AyaRoundfanSkill) and tgt.equips): return act 

744 

745 g = self.game 

746 pa = g.action_stack[-1] 

747 if not isinstance(pa, basic.BaseAttack): return act 

748 

749 cards = user_choose_cards(self, src, ('cards', 'showncards')) 

750 if not cards: return act 750 ↛ 751line 750 didn't jump to line 751, because the condition on line 750 was never false

751 g.process_action(DropCards(src, src, cards)) 

752 g.process_action(AyaRoundfan(src, tgt)) 

753 

754 return act 

755 

756 def cond(self, cards): 

757 if not len(cards) == 1 or cards[0].is_card(Skill): 757 ↛ 760line 757 didn't jump to line 760, because the condition on line 757 was never false

758 return False 

759 

760 return cards[0].resides_in.type in ('cards', 'showncards') 

761 

762 

763class Laevatein(UserAction): 

764 def apply_action(self): 

765 return True # logic handled in LaevateinHandler 

766 

767 

768class LaevateinSkill(WeaponSkill): 

769 range = 3 

770 skill_category = ['equip', 'passive'] 

771 associated_action = None 

772 target = t_None() 

773 

774 

775@register_eh 

776class LaevateinHandler(EventHandler): 

777 interested = ['attack_aftergraze'] 

778 card_usage = 'drop' 

779 

780 def handle(self, evt_type, arg): 

781 if evt_type == 'attack_aftergraze': 781 ↛ 801line 781 didn't jump to line 801, because the condition on line 781 was never false

782 act, succeed = arg 

783 assert isinstance(act, basic.BaseAttack) 

784 if succeed: 

785 return arg 

786 

787 src = act.source 

788 tgt = act.target 

789 if not src or not src.has_skill(LaevateinSkill): 789 ↛ 792line 789 didn't jump to line 792, because the condition on line 789 was never false

790 return arg 

791 

792 g = self.game 

793 cards = user_choose_cards(self, src, ('cards', 'showncards', 'equips')) 

794 if not cards: 

795 return arg 

796 

797 g.process_action(DropCards(src, src, cards)) 

798 g.process_action(Laevatein(src, tgt)) 

799 return act, True 

800 

801 return arg 

802 

803 def cond(self, cards): 

804 if not len(cards) == 2: return False 

805 

806 from thb.cards.definition import LaevateinCard 

807 for c in cards: 

808 t = c.resides_in.type 

809 if t not in ('cards', 'showncards', 'equips'): 

810 return False 

811 elif t == 'equips' and c.is_card(LaevateinCard): 

812 return False 

813 elif c.is_card(Skill): 

814 return False 

815 

816 return True 

817 

818 

819class DeathSickleSkill(WeaponSkill): 

820 range = 2 

821 skill_category = ['equip', 'passive'] 

822 associated_action = None 

823 target = t_None() 

824 

825 

826class DeathSickle(GenericAction): 

827 def __init__(self, act): 

828 self.action = act 

829 self.source, self.target = act.source, act.target 

830 

831 def apply_action(self): 

832 self.action.amount += 1 

833 return True 

834 

835 

836@register_eh 

837class DeathSickleHandler(EventHandler): 

838 interested = ['action_before'] 

839 execute_before = ['WineHandler'] 

840 

841 def handle(self, evt_type, act): 

842 if evt_type == 'action_before' and isinstance(act, Damage): 

843 from .basic import Attack 

844 g = self.game 

845 pact = g.action_stack[-1] 

846 if not isinstance(pact, Attack): return act 

847 src = act.source 

848 if not src or not src.has_skill(DeathSickleSkill): return act 

849 tgt = act.target 

850 if len(tgt.cards) + len(tgt.showncards) == 0: 

851 g.process_action(DeathSickle(act)) 

852 

853 return act 

854 

855 

856class KeystoneSkill(GreenUFOSkill): 

857 skill_category = ['equip', 'passive'] 

858 increment = 1 

859 

860 

861class Keystone(GenericAction): 

862 def __init__(self, act): 

863 assert isinstance(act, spellcard.Sinsack) 

864 self.source = self.target = act.target 

865 self.action = act 

866 

867 def apply_action(self): 

868 self.action.cancelled = True 

869 return True 

870 

871 

872@register_eh 

873class KeystoneHandler(EventHandler): 

874 interested = ['action_before'] 

875 execute_before = ['SaigyouBranchHandler', 'RejectHandler'] 

876 

877 def handle(self, evt_type, act): 

878 if evt_type == 'action_before' and isinstance(act, spellcard.Sinsack): 

879 tgt = act.target 

880 if tgt.has_skill(KeystoneSkill): 

881 self.game.process_action(Keystone(act)) 

882 

883 return act 

884 

885 

886class WitchBroomSkill(RedUFOSkill): 

887 skill_category = ['equip', 'passive'] 

888 increment = 2 

889 

890 

891class AccessoriesSkill(Skill): 

892 associated_action = None 

893 target = t_None() 

894 

895 

896class YinYangOrb(GenericAction): 

897 def __init__(self, ft): 

898 self.ftact = ft 

899 self.source = self.target = ft.target 

900 

901 def apply_action(self): 

902 ft = self.ftact 

903 tgt = ft.target 

904 

905 from .definition import YinYangOrbCard 

906 for e in tgt.equips: 906 ↛ 916line 906 didn't jump to line 916, because the loop on line 906 didn't complete

907 if e.is_card(YinYangOrbCard): 

908 with MigrateCardsTransaction(self) as trans: 

909 migrate_cards([ft.card], tgt.cards, unwrap=True, trans=trans) 

910 detach_cards([e], trans=trans) 

911 self.card = e 

912 ft.set_card(e, self) 

913 

914 break 

915 else: 

916 raise GameError('Player has YinYangOrb skill but no equip!') 

917 

918 return True 

919 

920 

921class YinYangOrbSkill(AccessoriesSkill): 

922 skill_category = ['equip', 'passive'] 

923 

924 

925@register_eh 

926class YinYangOrbHandler(EventHandler): 

927 interested = ['fatetell'] 

928 execute_after = ['FatetellMalleateHandler'] 

929 

930 def handle(self, evt_type, act): 

931 if evt_type == 'fatetell': 931 ↛ 940line 931 didn't jump to line 940, because the condition on line 931 was never false

932 g = self.game 

933 tgt = act.target 

934 if not tgt.has_skill(YinYangOrbSkill): return act 

935 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

936 return act 

937 

938 g.process_action(YinYangOrb(act)) 

939 

940 return act 

941 

942 

943class SuwakoHatSkill(AccessoriesSkill): 

944 skill_category = ['equip', 'passive'] 

945 

946 

947class SuwakoHatEffect(UserAction): 

948 def __init__(self, target, dcs): 

949 self.source = self.target = target 

950 self.dcs = dcs 

951 

952 def apply_action(self): 

953 self.dcs.dropn = max(self.dcs.dropn - 2, 0) 

954 return True 

955 

956 

957@register_eh 

958class SuwakoHatHandler(EventHandler): 

959 interested = ['action_before'] 

960 

961 def handle(self, evt_type, act): 

962 if evt_type == 'action_before' and isinstance(act, DropCardStage): 

963 tgt = act.target 

964 if tgt.has_skill(SuwakoHatSkill): 

965 self.game.process_action(SuwakoHatEffect(tgt, act)) 

966 

967 return act 

968 

969 

970class YoumuPhantomSkill(AccessoriesSkill): 

971 skill_category = ['equip', 'passive'] 

972 

973 

974class YoumuPhantomHeal(basic.Heal): 

975 pass 

976 

977 

978@register_eh 

979class YoumuPhantomHandler(EventHandler): 

980 interested = ['post_card_migration'] 

981 

982 def handle(self, evt_type, arg): 

983 if not evt_type == 'post_card_migration': return arg 983 ↛ exitline 983 didn't return from function 'handle', because the return on line 983 wasn't executed

984 

985 from .definition import YoumuPhantomCard 

986 trans = cast(MigrateCardsTransaction, arg) 

987 g = self.game 

988 

989 for m in trans.movements: 

990 if not m.card.is_card(YoumuPhantomCard): 

991 continue 

992 

993 if m.fr.type == 'equips': 

994 tgt = m.fr.owner 

995 g.process_action(MaxLifeChange(tgt, tgt, -1)) 

996 if not tgt.dead: 

997 g.process_action(YoumuPhantomHeal(tgt, tgt)) 

998 

999 if m.to.type == 'equips': 

1000 tgt = m.to.owner 

1001 g.process_action(MaxLifeChange(tgt, tgt, 1)) 

1002 

1003 return arg 

1004 

1005 

1006class IceWingSkill(RedUFOSkill): 

1007 skill_category = ['equip', 'passive'] 

1008 increment = 1 

1009 

1010 

1011class IceWing(GenericAction): 

1012 def __init__(self, act): 

1013 assert isinstance(act, (spellcard.SealingArray, spellcard.FrozenFrog)) 

1014 self.source = self.target = act.target 

1015 self.action = act 

1016 

1017 def apply_action(self): 

1018 self.action.cancelled = True 

1019 return True 

1020 

1021 

1022@register_eh 

1023class IceWingHandler(EventHandler): 

1024 interested = ['action_before'] 

1025 _effect_cls = spellcard.SealingArray, spellcard.FrozenFrog 

1026 

1027 execute_before = ['RejectHandler', 'SaigyouBranchHandler'] 

1028 

1029 def handle(self, evt_type, act): 

1030 if evt_type == 'action_before' and isinstance(act, self._effect_cls): 

1031 if act.target.has_skill(IceWingSkill): 

1032 self.game.process_action(IceWing(act)) 

1033 

1034 return act 

1035 

1036 

1037class GrimoireSkill(TreatAs, WeaponSkill): 

1038 skill_category = ['equip', 'active'] 

1039 range = 1 

1040 lookup_tbl = { 

1041 Card.SPADE: PhysicalCard.classes['DemonParadeCard'], # again... 

1042 Card.HEART: PhysicalCard.classes['FeastCard'], 

1043 Card.CLUB: PhysicalCard.classes['MapCannonCard'], 

1044 Card.DIAMOND: PhysicalCard.classes['HarvestCard'], 

1045 } 

1046 

1047 @property 

1048 def treat_as(self): 

1049 cl = self.associated_cards 

1050 if not (cl and cl[0].suit): 

1051 from .base import DummyCard 

1052 return DummyCard 

1053 return self.lookup_tbl[cl[0].suit] 

1054 

1055 def check(self): 

1056 cl = self.associated_cards 

1057 if not len(cl) == 1: return False 

1058 if not cl[0].resides_in.type in ('cards', 'showncards', 'equips'): 1058 ↛ 1059line 1058 didn't jump to line 1059, because the condition on line 1058 was never true

1059 return False 

1060 if not cl[0].suit: return False 1060 ↛ exitline 1060 didn't return from function 'check', because the return on line 1060 wasn't executed

1061 return True 

1062 

1063 

1064@register_eh 

1065class GrimoireHandler(EventHandler): 

1066 interested = ['action_after', 'action_shootdown'] 

1067 

1068 def handle(self, evt_type, act): 

1069 if evt_type == 'action_shootdown': 

1070 if not isinstance(act, LaunchCard): return act 

1071 c = act.card 

1072 if c.is_card(GrimoireSkill): 

1073 src = act.source 

1074 t = src.tags 

1075 

1076 if t['turn_count'] <= t['grimoire_tag']: 1076 ↛ 1077line 1076 didn't jump to line 1077, because the condition on line 1076 was never true

1077 raise ActionLimitExceeded 

1078 

1079 if t['vitality'] <= 0: 

1080 raise VitalityLimitExceeded 

1081 

1082 elif evt_type == 'action_after' and isinstance(act, LaunchCard): 

1083 c = act.card 

1084 if c.is_card(GrimoireSkill): 

1085 t = act.source.tags 

1086 t['vitality'] -= 1 

1087 t['grimoire_tag'] = t['turn_count'] 

1088 

1089 return act 

1090 

1091 

1092class SinsackHatAction(FatetellAction): 

1093 def __init__(self, source, target, hat_card): 

1094 self.source = source 

1095 self.target = target 

1096 self.hat_card = hat_card 

1097 self.fatetell_target = target 

1098 

1099 self.fatetell_cond = lambda c: c.suit == Card.SPADE and 1 <= c.number <= 8 

1100 

1101 def fatetell_action(self, ft): 

1102 if not ft.succeeded: 

1103 return False 

1104 

1105 g = self.game 

1106 tgt, c = self.target, self.hat_card 

1107 g.process_action(spellcard.SinsackDamage(None, tgt, amount=2)) 

1108 migrate_cards([c], tgt.cards, unwrap=True) 

1109 return True 

1110 

1111 

1112class SinsackHat(ShieldSkill): 

1113 skill_category = ['equip', 'passive'] 

1114 

1115 

1116@register_eh 

1117class SinsackHatHandler(EventHandler): 

1118 interested = ['action_after'] 

1119 

1120 def handle(self, evt_type, act): 

1121 if evt_type == 'action_after' and isinstance(act, FatetellStage): 

1122 tgt = act.target 

1123 g = self.game 

1124 from .definition import SinsackHatCard 

1125 for c in list(tgt.equips): 

1126 if not c.is_card(SinsackHatCard): 

1127 continue 

1128 

1129 g.process_action(SinsackHatAction(tgt, tgt, c)) 

1130 return act 

1131 

1132 return act