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 Sequence 

6 

7# -- third party -- 

8# -- own -- 

9from game.base import InputTransaction, sync_primitive 

10from thb.actions import ActionStage, Damage, DrawCardStage, DrawCards, DropCards, FatetellAction 

11from thb.actions import ForEach, LaunchCard, PlayerTurn, UserAction, ask_for_action, detach_cards 

12from thb.actions import migrate_cards, random_choose_card, register_eh, user_choose_cards 

13from thb.cards import basic 

14from thb.cards.base import Card 

15from thb.inputlets import ChooseIndividualCardInputlet, ChoosePeerCardInputlet 

16from thb.mode import THBEventHandler 

17from utils.check import CheckFailed, check 

18from utils.misc import BatchList, flatten 

19 

20 

21# -- code -- 

22class SpellCardAction(UserAction): 

23 non_responsive = False 

24 

25 

26class InstantSpellCardAction(SpellCardAction): 

27 pass 

28 

29 

30class Demolition(InstantSpellCardAction): 

31 # 城管执法 

32 

33 def apply_action(self): 

34 g = self.game 

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

36 

37 if not (tgt.cards or tgt.showncards or tgt.equips or tgt.fatetell): return False 

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

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

40 card = g.user_input([src], ChoosePeerCardInputlet(self, tgt, catnames)) 

41 if not card: 

42 card = random_choose_card(g, cats) 

43 if not card: 

44 return False 

45 

46 self.card = card 

47 g.players.exclude(tgt).reveal(card) 

48 g.process_action( 

49 DropCards(src, tgt, cards=[card]) 

50 ) 

51 return True 

52 

53 def is_valid(self): 

54 tgt = self.target 

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

56 return not tgt.dead and any(getattr(tgt, i) for i in catnames) 

57 

58 

59class Reject(InstantSpellCardAction): 

60 # 好人卡 

61 def __init__(self, source, target_act): 

62 self.source = source 

63 self.target_act = target_act 

64 self.target = target_act.target 

65 

66 def apply_action(self): 

67 if not isinstance(self.target_act, SpellCardAction): 

68 return False 

69 self.target_act.cancelled = True 

70 return True 

71 

72 

73@register_eh 

74class RejectHandler(THBEventHandler): 

75 interested = ['action_before'] 

76 card_usage = 'launch' 

77 

78 def handle(self, evt_type, act): 

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

80 if act.cancelled: return act # some other thing have done the job 

81 if act.non_responsive: 

82 return act 

83 

84 g = self.game 

85 

86 has_reject = False 

87 while g.is_server_side(): 

88 # from thb.characters.base import Character 

89 # from thb.characters.reimu import SpiritualAttack 

90 # for p in g.players: 

91 # if isinstance(p, Character) and p.has_skill(SpiritualAttack): 

92 # has_reject = True 

93 # break 

94 

95 # if has_reject: break 

96 

97 from thb.cards.definition import RejectCard 

98 for c in flatten([[p.cards, p.showncards] for p in g.players]): 

99 if isinstance(c, RejectCard): 

100 has_reject = True 

101 break 

102 

103 break 

104 

105 has_reject = sync_primitive(has_reject, g.players.player) 

106 if not has_reject: return act 

107 

108 self.target_act = act 

109 

110 pl = BatchList(p for p in g.players if not p.dead) 

111 

112 with InputTransaction('AskForRejectAction', pl) as trans: 

113 p, rst = ask_for_action(self, pl, ('cards', 'showncards'), [], trans=trans) 

114 

115 if not p: return act 

116 assert rst 

117 cards, _ = rst 

118 assert cards and self.cond(cards) 

119 g.process_action(LaunchCard(p, [act.target], cards[0], Reject(p, act))) 

120 

121 return act 

122 

123 def cond(self, cards: Sequence[Card]) -> bool: 

124 from thb.cards.definition import RejectCard 

125 try: 

126 check(len(cards) == 1) 

127 check(cards[0].is_card(RejectCard)) 

128 return True 

129 except CheckFailed: 

130 return False 

131 

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

133 act = self.target_act 

134 return LaunchCard(p, [act.target], cl[0], Reject(p, act)).can_fire() 

135 

136 

137class DelayedSpellCardAction(SpellCardAction): 

138 pass 

139 

140 

141class DelayedLaunchCard(UserAction): 

142 def apply_action(self): 

143 card = self.associated_card 

144 t = self.target 

145 migrate_cards([card], t.fatetell) 

146 

147 return True 

148 

149 def is_valid(self): 

150 if not self.associated_card: return False 

151 if not len(self.target_list) == 1: return False 

152 return not self.target.dead 

153 

154 

155class SealingArray(DelayedSpellCardAction, FatetellAction): 

156 # 封魔阵 

157 def __init__(self, source, target): 

158 self.source = source 

159 self.target = target 

160 self.fatetell_target = target 

161 

162 def fatetell_action(self, ft): 

163 g = self.game 

164 if ft.succeeded: 

165 turn = PlayerTurn.get_current(g) 

166 assert turn.target is self.target, (turn.target, self.target) 

167 try: 

168 turn.pending_stages.remove(ActionStage) 

169 except Exception: 

170 pass 

171 

172 return True 

173 

174 return False 

175 

176 def fatetell_cond(self, c: Card): 

177 return c.suit != Card.HEART 

178 

179 def fatetell_postprocess(self): 

180 g = self.game 

181 tgt = self.target 

182 g.process_action(DropCards(None, tgt, [self.associated_card])) 

183 

184 

185class NazrinRod(InstantSpellCardAction): 

186 # 纳兹琳的探宝棒 

187 

188 def apply_action(self): 

189 g = self.game 

190 g.process_action(DrawCards(self.target, amount=2)) 

191 return True 

192 

193 

194class SinsackDamage(Damage): 

195 pass 

196 

197 

198class Sinsack(DelayedSpellCardAction, FatetellAction): 

199 # 罪袋 

200 def __init__(self, source, target): 

201 self.source = source 

202 self.target = target 

203 self.fatetell_target = target 

204 

205 def fatetell_action(self, ft): 

206 if ft.succeeded: 

207 g = self.game 

208 g.process_action(SinsackDamage(None, self.target, amount=3)) 

209 return True 

210 

211 return False 

212 

213 def fatetell_cond(self, c: Card): 

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

215 

216 def fatetell_postprocess(self): 

217 g = self.game 

218 tgt = self.target 

219 if not self.cancelled and self.succeeded: 

220 g.process_action(DropCards(None, tgt, [self.associated_card])) 

221 else: 

222 pl = g.players 

223 stop = pl.index(tgt) 

224 next = stop - len(pl) + 1 

225 while next < stop: 

226 if not pl[next].dead: 

227 migrate_cards([self.associated_card], pl[next].fatetell) 

228 return 

229 next += 1 

230 if next == stop and len(pl) == 2: 

231 migrate_cards([self.associated_card], pl[next-1].fatetell) 

232 return 

233 

234 

235class YukariDimension(InstantSpellCardAction): 

236 # 紫的隙间 

237 

238 def apply_action(self): 

239 src = self.source 

240 tgt = self.target 

241 g = self.game 

242 

243 if not (tgt.cards or tgt.showncards or tgt.equips or tgt.fatetell): return False 

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

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

246 card = g.user_input([src], ChoosePeerCardInputlet(self, tgt, catnames)) 

247 if not card: 

248 card = random_choose_card(g, cats) 

249 if not card: 

250 return False 

251 

252 self.card = card 

253 src.reveal(card) 

254 migrate_cards([card], src.cards, unwrap=True) 

255 return True 

256 

257 def is_valid(self): 

258 if self.source.dead: 

259 return False 

260 

261 tgt = self.target 

262 catnames = ['cards', 'showncards', 'equips', 'fatetell'] 

263 return not tgt.dead and any(getattr(tgt, i) for i in catnames) 

264 

265 

266class BaseDuel(UserAction): 

267 # 弹幕战 

268 def __init__(self, source, target): 

269 self.source = source 

270 self.target = target 

271 

272 def apply_action(self): 

273 g = self.game 

274 source = self.source 

275 target = self.target 

276 

277 s, t = source, target 

278 while True: 

279 if t.dead: break 

280 if not g.process_action(basic.UseAttack(t)): break 

281 s, t = t, s 

282 

283 if not t.dead: 

284 g.process_action(Damage(s, t, amount=1)) 

285 

286 self.winner = s 

287 

288 return True 

289 

290 def is_valid(self): 

291 return not self.target.dead 

292 

293 

294class Duel(BaseDuel, InstantSpellCardAction): 

295 pass 

296 

297 

298class MapCannonEffect(InstantSpellCardAction): 

299 # 地图炮 

300 def apply_action(self): 

301 g = self.game 

302 source, target = self.source, self.target 

303 graze_action = basic.UseGraze(target) 

304 if not g.process_action(graze_action): 

305 g.process_action(Damage(source, target, amount=1)) 

306 return True 

307 else: 

308 return False 

309 

310 def is_valid(self): 

311 return not self.target.dead 

312 

313 

314class MapCannon(ForEach): 

315 action_cls = MapCannonEffect 

316 group_effect = True 

317 

318 

319class DemonParadeEffect(InstantSpellCardAction): 

320 def apply_action(self): 

321 g = self.game 

322 source, target = self.source, self.target 

323 use_action = basic.UseAttack(target) 

324 if not g.process_action(use_action): 

325 g.process_action(Damage(source, target, amount=1)) 

326 return True 

327 else: 

328 return False 

329 

330 def is_valid(self): 

331 return not self.target.dead 

332 

333 

334class DemonParade(ForEach): 

335 action_cls = DemonParadeEffect 

336 group_effect = True 

337 

338 

339class FeastEffect(InstantSpellCardAction): 

340 # 宴会 

341 def apply_action(self): 

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

343 g = self.game 

344 if tgt.life < tgt.maxlife: 

345 g.process_action(basic.Heal(src, tgt)) 

346 else: 

347 g.process_action(basic.Wine(src, tgt)) 

348 return True 

349 

350 def is_valid(self): 

351 return not self.target.dead 

352 

353 

354class Feast(ForEach): 

355 action_cls = FeastEffect 

356 group_effect = True 

357 

358 

359class HarvestEffect(InstantSpellCardAction): 

360 # 五谷丰登 效果 

361 def apply_action(self): 

362 g = self.game 

363 pact = ForEach.get_actual_action(self) 

364 cards = pact.cards 

365 cards_avail = [c for c in cards if c.detached] 

366 if not cards_avail: return False 

367 tgt = self.target 

368 

369 card = g.user_input( 

370 [tgt], 

371 ChooseIndividualCardInputlet(self, cards_avail), 

372 trans=pact.trans, 

373 ) or random_choose_card(g, [cards_avail]) 

374 

375 migrate_cards([card], tgt.cards) 

376 

377 pact.trans.notify('harvest_choose', card) 

378 self.card = card 

379 return True 

380 

381 def is_valid(self): 

382 try: 

383 cards = ForEach.get_actual_action(self).cards 

384 except Exception: 

385 return False 

386 

387 if self.target.dead: 

388 return False 

389 

390 return bool([c for c in cards if c.detached]) 

391 

392 

393class Harvest(ForEach): 

394 action_cls = HarvestEffect 

395 group_effect = True 

396 

397 def prepare(self): 

398 tl = self.target_list 

399 g = self.game 

400 cards = g.deck.getcards(len(tl)) 

401 g.players.reveal(cards) 

402 detach_cards(cards) 

403 trans = InputTransaction('HarvestChoose', g.players, cards=cards) 

404 trans.begin() 

405 self.cards = cards 

406 self.trans = trans 

407 

408 def cleanup(self): 

409 g = self.game 

410 self.trans.end() 

411 g.emit_event('harvest_finish', self) 

412 migrate_cards([c for c in self.cards if c.detached], g.deck.droppedcards) 

413 

414 

415class DollControl(InstantSpellCardAction): 

416 card_usage = 'launch' 

417 

418 def apply_action(self): 

419 tl = self.target_list 

420 assert len(tl) == 2 

421 src = self.source 

422 

423 attacker, victim = tl 

424 cards = user_choose_cards(self, attacker, ['cards', 'showncards']) 

425 g = self.game 

426 

427 if cards: 

428 g.players.reveal(cards) 

429 g.process_action(LaunchCard(attacker, [victim], cards[0])) 

430 else: 

431 l = [e for e in attacker.equips if e.equipment_category == 'weapon'] 

432 migrate_cards(l, src.cards) 

433 return True 

434 

435 def cond(self, cl): 

436 if len(cl) != 1: return False 

437 c = cl[0] 

438 if not c.associated_action: return False 

439 return issubclass(c.associated_action, basic.Attack) 

440 

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

442 attacker, victim = self.target_list 

443 return LaunchCard(attacker, [victim], cl[0]).can_fire() 

444 

445 def is_valid(self): 

446 if self.target.dead: 

447 return False 

448 

449 assert len(self.target_list) == 2 

450 

451 attacker, victim = self.target_list 

452 

453 if not any(e.equipment_category == 'weapon' for e in attacker.equips): 

454 return False 

455 

456 from .definition import AttackCard 

457 if not LaunchCard(attacker, [victim], AttackCard()).can_fire(): 

458 return False 

459 

460 return True 

461 

462 

463class DonationBoxEffect(InstantSpellCardAction): 

464 card_usage = 'handover' 

465 

466 def apply_action(self): 

467 t = self.target 

468 src = self.source 

469 g = self.game 

470 

471 if not (t.cards or t.showncards or t.equips): return False 

472 catnames = ('cards', 'showncards', 'equips') 

473 cats = [getattr(t, i) for i in catnames] 

474 cards = user_choose_cards(self, t, catnames) 

475 if not cards: 

476 cards = [random_choose_card(g, cats)] 

477 

478 if cards: 

479 g.players.exclude(t).reveal(cards) 

480 migrate_cards(cards, src.showncards) 

481 

482 return True 

483 

484 def cond(self, cards): 

485 from .base import Skill 

486 return len(cards) == 1 and cards[0].resides_in.type in ( 

487 'cards', 'showncards', 'equips' 

488 ) and not cards[0].is_card(Skill) 

489 

490 def is_valid(self): 

491 t = self.target 

492 if t.dead or self.source.dead: return False 

493 if t.cards or t.showncards or t.equips: return True 

494 return False 

495 

496 

497class DonationBox(ForEach): 

498 action_cls = DonationBoxEffect 

499 

500 def is_valid(self): 

501 tl = self.target_list 

502 if not 0 < len(tl) <= 2: return False 

503 

504 if not all( 

505 t.cards or t.showncards or t.equips 

506 for t in tl 

507 ): return False 

508 

509 return True 

510 

511 

512class FrozenFrog(DelayedSpellCardAction, FatetellAction): 

513 # 冻青蛙 

514 def __init__(self, source, target): 

515 self.source = source 

516 self.target = target 

517 self.fatetell_target = target 

518 

519 def fatetell_action(self, ft): 

520 g = self.game 

521 if ft.succeeded: 

522 turn = PlayerTurn.get_current(g) 

523 assert turn.target is self.target, (turn.target, self.target) 

524 try: 

525 turn.pending_stages.remove(DrawCardStage) 

526 except Exception: 

527 pass 

528 

529 return True 

530 

531 return False 

532 

533 def fatetell_cond(self, c: Card): 

534 return c.suit != Card.SPADE 

535 

536 def fatetell_postprocess(self): 

537 g = self.game 

538 tgt = self.target 

539 g.process_action(DropCards(None, tgt, [self.associated_card]))