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 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 thb.mode import THBEventHandler 

20from utils.check import CheckFailed, check 

21from utils.misc import classmix 

22 

23 

24# -- code -- 

25class WearEquipmentAction(UserAction): 

26 def apply_action(self): 

27 g = self.game 

28 card = self.associated_card 

29 

30 from thb.cards.definition import EquipmentCard 

31 assert isinstance(card, EquipmentCard) 

32 

33 tgt = self.target 

34 equips = tgt.equips 

35 

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

37 assert _s is self 

38 assert _t is tgt 

39 assert _c is card 

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

41 

42 if rst == 'handled': 

43 return True 

44 

45 for oc in list(equips): 

46 if oc.equipment_category == card.equipment_category: 

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

48 

49 migrate_cards([card], tgt.equips) 

50 

51 return True 

52 

53 

54class ReforgeWeapon(Reforge): 

55 pass 

56 

57 

58@register_eh 

59class WeaponReforgeHandler(THBEventHandler): 

60 interested = ['action_before'] 

61 

62 def handle(self, evt_type, act): 

63 if evt_type == 'action_before' and isinstance(act, ActionStageLaunchCard): 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true

64 c = act.card 

65 tgt = act.target 

66 

67 if c.is_card(VirtualCard): return act 

68 from thb.cards.definition import EquipmentCard 

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

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

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

72 

73 g = self.game 

74 

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

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

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

78 act.cancelled = True 

79 

80 return act 

81 

82 

83@register_eh 

84class EquipmentTransferHandler(THBEventHandler): 

85 interested = ['post_card_migration'] 

86 

87 def handle(self, evt, trans): 

88 if evt == 'post_card_migration': 

89 from thb.cards.definition import EquipmentCard 

90 assert isinstance(trans, MigrateCardsTransaction) 

91 for m in trans.movements: 

92 c = m.card 

93 if not isinstance(c, EquipmentCard): 

94 continue 

95 

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

97 owner = m.fr.owner 

98 assert owner 

99 

100 try: 

101 owner.skills.remove(c.equipment_skill) 

102 except ValueError: 

103 pass 

104 

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

106 owner = m.to.owner 

107 assert owner 

108 if c.equipment_skill: 

109 owner.skills.append(c.equipment_skill) 

110 

111 return trans 

112 

113 

114class ShieldSkill(Skill): 

115 associated_action = None 

116 target = t_None() 

117 

118 

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

120 treat_as = PhysicalCard.classes['GrazeCard'] 

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

122 

123 def check(self): 

124 return False 

125 

126 

127class OpticalCloak(FatetellAction): 

128 # 光学迷彩 

129 def __init__(self, source, target): 

130 self.source = source 

131 self.target = target 

132 self.fatetell_target = target 

133 

134 def fatetell_action(self, ft): 

135 self.fatetell_card = ft.card 

136 return bool(ft.succeeded) 

137 

138 def fatetell_cond(self, card: Card): 

139 return card.color == Card.RED 

140 

141 

142@register_eh 

143class OpticalCloakHandler(THBEventHandler): 

144 interested = ['action_apply'] 

145 execute_after = ['AssistedGrazeHandler'] 

146 

147 def handle(self, evt_type, act): 

148 from .basic import BaseUseGraze 

149 if evt_type == 'action_apply' and isinstance(act, BaseUseGraze): 149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true

150 tgt = act.target 

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

152 if act.card: return act 

153 

154 g = self.game 

155 

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

157 return act 

158 

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

160 act.card = OpticalCloakSkill(tgt) 

161 

162 return act 

163 

164 

165class MomijiShieldSkill(ShieldSkill): 

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

167 

168 

169class MomijiShield(GenericAction): 

170 def __init__(self, act): 

171 self.action = act 

172 self.source = self.target = act.target 

173 

174 def apply_action(self): 

175 self.action.cancelled = True 

176 return True 

177 

178 

179@register_eh 

180class MomijiShieldHandler(THBEventHandler): 

181 interested = ['action_before'] 

182 execute_before = ['HouraiJewelHandler'] 

183 

184 def handle(self, evt_type, act): 

185 from .basic import BaseAttack 

186 if not (evt_type == 'action_before' and isinstance(act, BaseAttack)): return act 186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never false

187 tgt = act.target 

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

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

190 g = self.game 

191 g.process_action(MomijiShield(act)) 

192 

193 return act 

194 

195 

196class UFOSkill(Skill): 

197 associated_action = None 

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

199 target = t_None() 

200 

201 

202class GreenUFOSkill(UFOSkill): 

203 increment = 1 

204 

205 

206class RedUFOSkill(UFOSkill): 

207 increment = 1 

208 

209 

210@register_eh 

211class UFODistanceHandler(THBEventHandler): 

212 interested = ['calcdistance'] 

213 

214 def handle(self, evt_type, arg): 

215 if not evt_type == 'calcdistance': return arg 

216 

217 src, card, dist = arg 

218 for s in src.skills: 

219 if not issubclass(s, RedUFOSkill): continue 

220 if not src.has_skill(s): continue 

221 incr = s.increment 

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

223 for p in dist: 

224 dist[p] -= incr 

225 

226 for p in dist: 

227 for s in p.skills: 

228 if not issubclass(s, GreenUFOSkill): continue 

229 if not p.has_skill(s): continue 

230 incr = s.increment 

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

232 

233 return arg 

234 

235 

236class WeaponSkill(Skill): 

237 range = 1 

238 

239 

240class RoukankenSkill(WeaponSkill): 

241 associated_action = None 

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

243 target = t_None() 

244 range = 3 

245 

246 

247class Roukanken(GenericAction): 

248 

249 def apply_action(self): 

250 tgt = self.target 

251 

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

253 for s in skills: 

254 tgt.disable_skill(s, 'roukanken') 

255 

256 return True 

257 

258 

259@register_eh 

260class RoukankenEffectHandler(THBEventHandler): 

261 interested = ['action_before', 'action_done'] 

262 execute_before = [ 

263 'MomijiShieldHandler', 

264 'OpticalCloakHandler', 

265 'SaigyouBranchHandler', 

266 'HouraiJewelHandler', 

267 'SpearTheGungnirHandler', 

268 'HakuroukenHandler', 

269 'FreakingPowerHandler', 

270 'ResonanceHandler' 

271 ] 

272 

273 def handle(self, evt_type, act): 

274 if evt_type == 'action_before' and isinstance(act, basic.BaseAttack): 274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true

275 cls = self.__class__ 

276 g = self.game 

277 

278 if act.cancelled: 

279 return act 

280 

281 if act._[cls] == 'roukanken-processed': 

282 return act 

283 

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

285 

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

287 if src.has_skill(RoukankenSkill): 

288 g.process_action(Roukanken(src, tgt)) 

289 

290 if evt_type == 'action_done' and isinstance(act, basic.BaseAttack): 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true

291 act.target.reenable_skill('roukanken') 

292 

293 return act 

294 

295 

296class NenshaPhoneSkill(WeaponSkill): 

297 associated_action = None 

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

299 target = t_None() 

300 range = 4 

301 

302 

303class NenshaPhone(GenericAction): 

304 def apply_action(self): 

305 tgt = self.target 

306 

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

308 g = self.game 

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

310 migrate_cards(cards, tgt.showncards) 

311 

312 return True 

313 

314 

315@register_eh 

316class NenshaPhoneHandler(THBEventHandler): 

317 interested = ['action_after'] 

318 

319 def handle(self, evt_type, act): 

320 if evt_type == 'action_after' and isinstance(act, Damage): 320 ↛ 321line 320 didn't jump to line 321, because the condition on line 320 was never true

321 if not act.succeeded: return act 

322 g = self.game 

323 pa = g.action_stack[-1] 

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

325 

326 src = act.source 

327 tgt = act.target 

328 if tgt.dead: return act 

329 if not tgt.cards: return act 

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

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

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

333 

334 return act 

335 

336 

337class ElementalReactorSkill(WeaponSkill): 

338 associated_action = None 

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

340 target = t_None() 

341 range = 1 

342 

343 

344@register_eh 

345class ElementalReactorHandler(THBEventHandler): 

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

347 execute_after = ['EquipmentTransferHandler'] 

348 

349 def handle(self, evt_type, arg): 

350 if evt_type == 'action_stage_action': 

351 tgt = arg 

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

353 basic.AttackCardVitalityHandler.disable(tgt) 

354 

355 elif evt_type == 'post_card_migration': 

356 trans = cast(MigrateCardsTransaction, arg) 

357 from .definition import ElementalReactorCard 

358 

359 for m in trans.movements: 

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

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

362 

363 return arg 

364 

365 

366class GungnirSkill(TreatAs, WeaponSkill): 

367 target = t_OtherOne() 

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

369 range = 3 

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

371 

372 def check(self): 

373 cl = self.associated_cards 

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

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

376 if not all(c.is_card(PhysicalCard) for c in cl): return False 

377 return len(cl) == 2 

378 

379 

380class ScarletRhapsody(ForEach): 

381 action_cls = basic.Attack 

382 

383 

384class ScarletRhapsodySkill(WeaponSkill): 

385 range = 4 

386 associated_action = ScarletRhapsody 

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

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

389 target = t_OtherLessEqThanN(3) 

390 usage = 'launch' 

391 

392 def check(self): 

393 try: 

394 cl = self.associated_cards 

395 check(len(cl) == 1) 

396 card = cl[0] 

397 from .definition import AttackCard 

398 check(card.is_card(AttackCard)) 

399 tgt = card.resides_in.owner 

400 check(card.is_card(PhysicalCard)) 

401 

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

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

404 

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

406 

407 return True 

408 except CheckFailed: 

409 return False 

410 

411 def is_card(self, cls): 

412 from thb.cards.definition import AttackCard 

413 if issubclass(AttackCard, cls): return True 

414 return isinstance(self, cls) 

415 

416 @property 

417 def distance(self): 

418 cl = self.associated_cards 

419 if not cl: 

420 return 1 

421 c = cl[0] 

422 return max(1, getattr(c, 'distance', 1)) 

423 

424 

425class RepentanceStickSkill(WeaponSkill): 

426 range = 2 

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

428 associated_action = None 

429 target = t_None() 

430 

431 

432class RepentanceStick(GenericAction): 

433 def apply_action(self) -> bool: 

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

435 g = self.game 

436 

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

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

439 

440 l: List[PhysicalCard] = [] 

441 for i in range(2): 

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

443 break 

444 

445 card = g.user_input( 

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

447 ) 

448 

449 if not card: 

450 card = random_choose_card(g, cats) 

451 if card: 

452 l.append(card) 

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

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

455 

456 self.cards = l 

457 return True 

458 

459 

460@register_eh 

461class RepentanceStickHandler(THBEventHandler): 

462 interested = ['action_before'] 

463 execute_before = ['WineHandler'] 

464 

465 def handle(self, evt_type, act): 

466 if evt_type == 'action_before' and isinstance(act, Damage): 466 ↛ 467line 466 didn't jump to line 467, because the condition on line 466 was never true

467 if act.cancelled: return act 

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

469 if src and src.has_skill(RepentanceStickSkill): 

470 g = self.game 

471 pa = g.action_stack[-1] 

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

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

474 return act 

475 

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

477 return act 

478 

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

480 act.cancelled = True 

481 

482 return act 

483 

484 

485class IbukiGourdSkill(RedUFOSkill): 

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

487 increment = 0 

488 

489 

490@register_eh 

491class IbukiGourdHandler(THBEventHandler): 

492 interested = ['action_apply', 'action_after', 'card_migration'] 

493 

494 def handle(self, evt_type, act): 

495 if evt_type == 'action_after' and isinstance(act, Damage): 495 ↛ 496line 495 didn't jump to line 496, because the condition on line 495 was never true

496 src = act.source 

497 if not src: return act 

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

499 

500 g = self.game 

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

502 

503 elif evt_type == 'action_apply' and isinstance(act, FinalizeStage): 503 ↛ 504line 503 didn't jump to line 504, because the condition on line 503 was never true

504 tgt = act.target 

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

506 

507 g = self.game 

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

509 

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

511 

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

513 from .definition import IbukiGourdCard 

514 trans = cast(MigrateCardsTransaction, act) 

515 

516 for m in trans.movements: 

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

518 assert m.to.owner 

519 tgt = m.to.owner 

520 g = self.game 

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

522 

523 return act 

524 

525 

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

527 def apply_action(self): 

528 g = self.game 

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

530 return True 

531 

532 

533class HouraiJewelSkill(WeaponSkill): 

534 associated_action = None 

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

536 target = t_None() 

537 range = 1 

538 

539 

540@register_eh 

541class HouraiJewelHandler(THBEventHandler): 

542 interested = ['action_before'] 

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

544 

545 def handle(self, evt_type, act): 

546 if evt_type == 'action_before' and isinstance(act, basic.Attack): 546 ↛ 547line 546 didn't jump to line 547, because the condition on line 546 was never true

547 src = act.source 

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

549 if isinstance(act, HouraiJewelAttack): return act 

550 g = self.game 

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

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

553 

554 return act 

555 

556 

557class UmbrellaSkill(ShieldSkill): 

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

559 

560 

561class UmbrellaEffect(GenericAction): 

562 def __init__(self, act, damage_act): 

563 self.source = self.target = damage_act.target 

564 self.action = act 

565 self.damage_act = damage_act 

566 

567 def apply_action(self): 

568 self.damage_act.cancelled = True 

569 return True 

570 

571 

572@register_eh 

573class UmbrellaHandler(THBEventHandler): 

574 # 紫的阳伞 

575 interested = ['action_before'] 

576 execute_before = ['RejectHandler'] 

577 

578 def handle(self, evt_type, act): 

579 if evt_type == 'action_before' and isinstance(act, Damage): 579 ↛ 580line 579 didn't jump to line 580, because the condition on line 579 was never true

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

581 g = self.game 

582 pact = g.action_stack[-1] 

583 

584 if isinstance(pact, spellcard.SpellCardAction): 

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

586 

587 return act 

588 

589 

590class MaidenCostume(TreatAs, ShieldSkill): 

591 treat_as = PhysicalCard.classes['RejectCard'] 

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

593 

594 def check(self): 

595 return False 

596 

597 

598class MaidenCostumeAction(FatetellAction): 

599 def __init__(self, source, act): 

600 self.source = source 

601 self.target = source 

602 self.fatetell_target = source 

603 self.act = act 

604 

605 def fatetell_action(self, ft): 

606 act = self.act 

607 src = self.source 

608 

609 g = self.game 

610 if ft.succeeded: 

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

612 g.process_action(LaunchCard( 

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

614 )) 

615 return True 

616 else: 

617 return False 

618 

619 def fatetell_cond(self, c: Card): 

620 return 9 <= c.number <= 13 

621 

622 

623@register_eh 

624class MaidenCostumeHandler(THBEventHandler): 

625 interested = ['action_before'] 

626 execute_before = ['RejectHandler'] 

627 execute_after = ['HouraiJewelHandler'] 

628 

629 def handle(self, evt_type, act): 

630 if evt_type == 'action_before' and isinstance(act, spellcard.SpellCardAction): 630 ↛ 631line 630 didn't jump to line 631, because the condition on line 630 was never true

631 tgt = act.target 

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

633 if act.cancelled: return act 

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

635 g = self.game 

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

637 return act 

638 

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

640 

641 return act 

642 

643 

644class HakuroukenSkill(WeaponSkill): 

645 range = 2 

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

647 associated_action = None 

648 target = t_None() 

649 

650 

651class Hakurouken(GenericAction): 

652 card_usage = 'drop' 

653 

654 def apply_action(self): 

655 src = self.source 

656 tgt = self.target 

657 

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

659 g = self.game 

660 if cards: 

661 self.peer_action = 'drop' 

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

663 else: 

664 self.peer_action = 'draw' 

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

666 

667 return True 

668 

669 def cond(self, cards): 

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

671 

672 

673@register_eh 

674class HakuroukenHandler(THBEventHandler): 

675 interested = ['action_before'] 

676 

677 # BUG WITH OUT THIS LINE: 

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

679 # then src drops Gourd, 

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

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

682 

683 def handle(self, evt_type, act): 

684 if evt_type == 'action_before' and isinstance(act, basic.BaseAttack): 684 ↛ 685line 684 didn't jump to line 685, because the condition on line 684 was never true

685 if act.cancelled: return act 

686 src = act.source 

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

688 card = act.associated_card 

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

690 

691 g = self.game 

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

693 return act 

694 

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

696 

697 return act 

698 

699 

700class AyaRoundfan(GenericAction): 

701 def apply_action(self): 

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

703 g = self.game 

704 

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

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

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

708 self.card = equip 

709 

710 return True 

711 

712 def is_valid(self): 

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

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

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

716 # 然后弃了自己的装备 

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

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

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

720 # 发动了小小军势 

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

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

723 # 0:28:19 

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

725 # 喜闻乐见的插入结算 

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

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

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

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

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

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

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

733 # 崩 

734 

735 # 所以加上这个 

736 return self.target.equips 

737 

738 

739class AyaRoundfanSkill(WeaponSkill): 

740 range = 5 

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

742 associated_action = None 

743 target = t_None() 

744 

745 

746@register_eh 

747class AyaRoundfanHandler(THBEventHandler): 

748 interested = ['action_after'] 

749 execute_after = ['DyingHandler'] 

750 card_usage = 'drop' 

751 

752 def handle(self, evt_type, act): 

753 if evt_type == 'action_after' and isinstance(act, Damage): 753 ↛ 754line 753 didn't jump to line 754, because the condition on line 753 was never true

754 if not act.succeeded: return act 

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

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

757 

758 g = self.game 

759 pa = g.action_stack[-1] 

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

761 

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

763 if not cards: return act 

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

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

766 

767 return act 

768 

769 def cond(self, cards): 

770 if not len(cards) == 1 or cards[0].is_card(Skill): 

771 return False 

772 

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

774 

775 

776class Laevatein(UserAction): 

777 def apply_action(self): 

778 return True # logic handled in LaevateinHandler 

779 

780 

781class LaevateinSkill(WeaponSkill): 

782 range = 3 

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

784 associated_action = None 

785 target = t_None() 

786 

787 

788@register_eh 

789class LaevateinHandler(THBEventHandler): 

790 interested = ['attack_aftergraze'] 

791 card_usage = 'drop' 

792 

793 def handle(self, evt_type, arg): 

794 if evt_type == 'attack_aftergraze': 

795 act, succeed = arg 

796 assert isinstance(act, basic.BaseAttack) 

797 if succeed: 

798 return arg 

799 

800 src = act.source 

801 tgt = act.target 

802 if not src or not src.has_skill(LaevateinSkill): 

803 return arg 

804 

805 g = self.game 

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

807 if not cards: 

808 return arg 

809 

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

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

812 return act, True 

813 

814 return arg 

815 

816 def cond(self, cards): 

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

818 

819 from thb.cards.definition import LaevateinCard 

820 for c in cards: 

821 t = c.resides_in.type 

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

823 return False 

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

825 return False 

826 elif c.is_card(Skill): 

827 return False 

828 

829 return True 

830 

831 

832class DeathSickleSkill(WeaponSkill): 

833 range = 2 

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

835 associated_action = None 

836 target = t_None() 

837 

838 

839class DeathSickle(GenericAction): 

840 def __init__(self, act): 

841 self.action = act 

842 self.source, self.target = act.source, act.target 

843 

844 def apply_action(self): 

845 self.action.amount += 1 

846 return True 

847 

848 

849@register_eh 

850class DeathSickleHandler(THBEventHandler): 

851 interested = ['action_before'] 

852 execute_before = ['WineHandler'] 

853 

854 def handle(self, evt_type, act): 

855 if evt_type == 'action_before' and isinstance(act, Damage): 855 ↛ 856line 855 didn't jump to line 856, because the condition on line 855 was never true

856 from .basic import Attack 

857 g = self.game 

858 pact = g.action_stack[-1] 

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

860 src = act.source 

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

862 tgt = act.target 

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

864 g.process_action(DeathSickle(act)) 

865 

866 return act 

867 

868 

869class KeystoneSkill(GreenUFOSkill): 

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

871 increment = 1 

872 

873 

874class Keystone(GenericAction): 

875 def __init__(self, act): 

876 assert isinstance(act, spellcard.Sinsack) 

877 self.source = self.target = act.target 

878 self.action = act 

879 

880 def apply_action(self): 

881 self.action.cancelled = True 

882 return True 

883 

884 

885@register_eh 

886class KeystoneHandler(THBEventHandler): 

887 interested = ['action_before'] 

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

889 

890 def handle(self, evt_type, act): 

891 if evt_type == 'action_before' and isinstance(act, spellcard.Sinsack): 891 ↛ 892line 891 didn't jump to line 892, because the condition on line 891 was never true

892 tgt = act.target 

893 if tgt.has_skill(KeystoneSkill): 

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

895 

896 return act 

897 

898 

899class WitchBroomSkill(RedUFOSkill): 

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

901 increment = 2 

902 

903 

904class AccessoriesSkill(Skill): 

905 associated_action = None 

906 target = t_None() 

907 

908 

909class YinYangOrb(GenericAction): 

910 def __init__(self, ft): 

911 self.ftact = ft 

912 self.source = self.target = ft.target 

913 

914 def apply_action(self): 

915 ft = self.ftact 

916 tgt = ft.target 

917 

918 from .definition import YinYangOrbCard 

919 for e in tgt.equips: 

920 if e.is_card(YinYangOrbCard): 

921 with MigrateCardsTransaction(self) as trans: 

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

923 migrate_cards([e], tgt.special, trans=trans) 

924 

925 detach_cards([e], trans=trans) 

926 self.card = e 

927 ft.set_card(e, self) 

928 

929 break 

930 else: 

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

932 

933 return True 

934 

935 

936class YinYangOrbSkill(AccessoriesSkill): 

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

938 

939 

940@register_eh 

941class YinYangOrbHandler(THBEventHandler): 

942 interested = ['fatetell'] 

943 execute_after = ['FatetellMalleateHandler'] 

944 

945 def handle(self, evt_type, act): 

946 if evt_type == 'fatetell': 

947 g = self.game 

948 tgt = act.target 

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

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

951 return act 

952 

953 g.process_action(YinYangOrb(act)) 

954 

955 return act 

956 

957 

958class SuwakoHatSkill(AccessoriesSkill): 

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

960 

961 

962class SuwakoHatEffect(UserAction): 

963 def __init__(self, target, dcs): 

964 self.source = self.target = target 

965 self.dcs = dcs 

966 

967 def apply_action(self): 

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

969 return True 

970 

971 

972@register_eh 

973class SuwakoHatHandler(THBEventHandler): 

974 interested = ['action_before'] 

975 

976 def handle(self, evt_type, act): 

977 if evt_type == 'action_before' and isinstance(act, DropCardStage): 977 ↛ 978line 977 didn't jump to line 978, because the condition on line 977 was never true

978 tgt = act.target 

979 if tgt.has_skill(SuwakoHatSkill): 

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

981 

982 return act 

983 

984 

985class YoumuPhantomSkill(AccessoriesSkill): 

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

987 

988 

989class YoumuPhantomHeal(basic.Heal): 

990 pass 

991 

992 

993@register_eh 

994class YoumuPhantomHandler(THBEventHandler): 

995 interested = ['post_card_migration'] 

996 

997 def handle(self, evt_type, arg): 

998 if not evt_type == 'post_card_migration': return arg 

999 

1000 from .definition import YoumuPhantomCard 

1001 trans = cast(MigrateCardsTransaction, arg) 

1002 g = self.game 

1003 

1004 for m in trans.movements: 

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

1006 continue 

1007 

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

1009 tgt = m.fr.owner 

1010 assert tgt 

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

1012 if not tgt.dead: 

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

1014 

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

1016 tgt = m.to.owner 

1017 assert tgt 

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

1019 

1020 return arg 

1021 

1022 

1023class IceWingSkill(RedUFOSkill): 

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

1025 increment = 1 

1026 

1027 

1028class IceWing(GenericAction): 

1029 def __init__(self, act): 

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

1031 self.source = self.target = act.target 

1032 self.action = act 

1033 

1034 def apply_action(self): 

1035 self.action.cancelled = True 

1036 return True 

1037 

1038 

1039@register_eh 

1040class IceWingHandler(THBEventHandler): 

1041 interested = ['action_before'] 

1042 _effect_cls = spellcard.SealingArray, spellcard.FrozenFrog 

1043 

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

1045 

1046 def handle(self, evt_type, act): 

1047 if evt_type == 'action_before' and isinstance(act, self._effect_cls): 1047 ↛ 1048line 1047 didn't jump to line 1048, because the condition on line 1047 was never true

1048 if act.target.has_skill(IceWingSkill): 

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

1050 

1051 return act 

1052 

1053 

1054class GrimoireSkill(TreatAs, WeaponSkill): 

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

1056 range = 1 

1057 lookup_tbl = { 

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

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

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

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

1062 } 

1063 

1064 @property 

1065 def treat_as(self): 

1066 cl = self.associated_cards 

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

1068 from .base import DummyCard 

1069 return DummyCard 

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

1071 

1072 def check(self): 

1073 cl = self.associated_cards 

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

1075 if not cl[0].resides_in.type in ('cards', 'showncards', 'equips'): 

1076 return False 

1077 if not cl[0].suit: return False 

1078 return True 

1079 

1080 

1081@register_eh 

1082class GrimoireHandler(THBEventHandler): 

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

1084 

1085 def handle(self, evt_type, act): 

1086 if evt_type == 'action_shootdown': 

1087 if not isinstance(act, LaunchCard): return act 1087 ↛ 1088line 1087 didn't jump to line 1088, because the condition on line 1087 was never false

1088 c = act.card 

1089 if c.is_card(GrimoireSkill): 

1090 src = act.source 

1091 t = src.tags 

1092 

1093 if t['turn_count'] <= t['grimoire_tag']: 

1094 raise ActionLimitExceeded 

1095 

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

1097 raise VitalityLimitExceeded 

1098 

1099 elif evt_type == 'action_after' and isinstance(act, LaunchCard): 1099 ↛ 1100line 1099 didn't jump to line 1100, because the condition on line 1099 was never true

1100 c = act.card 

1101 if c.is_card(GrimoireSkill): 

1102 t = act.source.tags 

1103 t['vitality'] -= 1 

1104 t['grimoire_tag'] = t['turn_count'] 

1105 

1106 return act 

1107 

1108 

1109class SinsackHatAction(FatetellAction): 

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

1111 self.source = source 

1112 self.target = target 

1113 self.hat_card = hat_card 

1114 self.fatetell_target = target 

1115 

1116 def fatetell_action(self, ft): 

1117 if not ft.succeeded: 

1118 return False 

1119 

1120 g = self.game 

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

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

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

1124 return True 

1125 

1126 def fatetell_cond(self, c: Card): 

1127 return c.suit == Card.SPADE and 1 <= c.number <= 8 

1128 

1129 

1130class SinsackHat(ShieldSkill): 

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

1132 

1133 

1134@register_eh 

1135class SinsackHatHandler(THBEventHandler): 

1136 interested = ['action_after'] 

1137 

1138 def handle(self, evt_type, act): 

1139 if evt_type == 'action_after' and isinstance(act, FatetellStage): 1139 ↛ 1140line 1139 didn't jump to line 1140, because the condition on line 1139 was never true

1140 tgt = act.target 

1141 g = self.game 

1142 from .definition import SinsackHatCard 

1143 for c in list(tgt.equips): 

1144 if not c.is_card(SinsackHatCard): 

1145 continue 

1146 

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

1148 return act 

1149 

1150 return act