# Coverage for thb/meta/cards/spellcard.py : 48%

## 235 statements 117 run 118 missing 0 excluded 0 partial

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1# -*- coding: utf-8 -*-

2 from __future__ import annotations

3

4# -- stdlib --

5 import itertools

6 import random

7

8# -- third party --

9# -- own --

10 from thb.cards import definition, spellcard

11 from thb.cards.base import PhysicalCard

12 from thb.meta.common import ui_meta

13

14

15# -- code --

16@ui_meta(definition.DemolitionCard)

17 class DemolitionCard:

18 # action_stage meta

19 image = 'thb-card-demolition'

20 name = '城管执法'

21 description = (

22 '|R城管执法|r\n\n'

23 '非延时符卡。\n'

24 '出牌阶段，对一名其他角色使用，弃置其区域内的一张牌。\n\n'

25 '|DB（画师：霏茶，CV：shourei小N）|r'

26 )

27

28 def is_action_valid(self, c, tl):

29 if not tl:

30 return (False, '请选择拆除目标')

31

32 tgt = tl[0]

33 if not sum([len(l) for l in [tgt.cards, tgt.showncards, tgt.equips, tgt.fatetell]]):

34 return (False, '这货已经没有牌了')

35 else:

36 return (True, '嗯，你的牌太多了')

37

38 def sound_effect(self, act):

39 return 'thb-cv-card_demolition'

40

41

42@ui_meta(spellcard.Demolition)

43 class Demolition:

44 def effect_string(self, act):

45 if not act.succeeded: return None

46

47 # This could happen when:

48 # 1. Parsee uses Envy, recycled a diamond card

49 # 2. The card is an equipment weared by Alice

50 # 3. Alice fires DollBlast, drops the very same card

51 # 4. Sometime here, cards are shuffled

52 # 5. Demolition now holds a ShreddedCard

53 if not isinstance(act.card, PhysicalCard):

54 return None

55

56 return '|G【%s】|r卸掉了|G【%s】|r的%s。' % (

57 act.source.ui_meta.name,

58 act.target.ui_meta.name,

59 self.card_desc(act.card),

60 )

61

62

63@ui_meta(definition.RejectCard)

64 class RejectCard:

65 # action_stage meta

66 name = '好人卡'

67 image = 'thb-card-reject'

68 description = (

69 '|R好人卡|r\n\n'

70 '非延时符卡。\n'

71 '一张符卡对一名目标角色生效前，你可以使用此牌来抵消该符卡对其目标角色产生的效果。\n\n'

72 '|DB（画师：霏茶，CV：VV）'

73 )

74

75 def is_action_valid(self, c, tl):

76 return (False, '你不能主动出好人卡')

77

78 def effect_string(self, act):

79 return '|G【%s】|r为|G【%s】|r受到的|G%s|r使用了|G%s|r。' % (

80 act.source.ui_meta.name,

81 act.target.ui_meta.name,

82 act.force_action.target_act.associated_card.ui_meta.name,

83 act.card.ui_meta.name,

84 )

85

86 def sound_effect(self, act):

87 return 'thb-cv-card_reject'

88

89 def has_reject_card(self, p):

90 from thb.cards.definition import RejectCard as RC

91 if any([c.is_card(RC) for c in itertools.chain(p.cards, p.showncards)]):

92 return True

93

94 from thb.characters import reimu

95 if p.has_skill(reimu.SpiritualAttack) and not p.dead:

96 return True

97

98 return False

99

100

101@ui_meta(spellcard.RejectHandler)

102 class RejectHandler:

103 # choose_card meta

104 def choose_card_text(self, act, cards):

105 c = act.target_act.associated_card

106 name = c.ui_meta.name

107

108 s = '【%s】受到的【%s】' % (

109 act.target_act.target.ui_meta.name,

110 name,

111 )

112

113 if act.cond(cards):

114 return (True, '对不起，你是一个好人(%s)' % s)

115 else:

116 return (False, '请选择一张好人卡（%s)' % s)

117

118

119@ui_meta(definition.SealingArrayCard)

120 class SealingArrayCard:

121 # action_stage meta

122 name = '封魔阵'

123 image = 'thb-card-sealarray'

124 tag = 'thb-tag-sealarray'

125 description = (

126 '|R封魔阵|r\n\n'

127 '延时类符卡。\n'

128 '出牌阶段，对一名其他角色使用，将此牌横置于该角色的判定区内。该角色的判定阶段，需进行一次判定然后弃置此牌。若判定结果不为红桃，跳过其出牌阶段。\n'

129 '|B|R>> |r判定开始前,你可以使用【好人卡】抵消该符卡的效果（抵消后弃掉【封魔阵】）。\n\n'

130 '|DB（画师：霏茶，CV：shourei小N）|r'

131 )

132

133 def is_action_valid(self, c, tl):

134 if len(tl) != 1:

135 return (False, '请选择封魔阵的目标')

136 t = tl[0]

137 if self.me is t:

138 return (False, '你不能跟自己过不去啊！')

139

140 return (True, '画个圈圈诅咒你！')

141

142 def sound_effect(self, act):

143 return 'thb-cv-card_sealarray'

144

145

146@ui_meta(spellcard.SealingArray)

147 class SealingArray:

148 def effect_string(self, act):

149 tgt = act.target

150 if act.succeeded:

151 return '|G【%s】|r被困在了封魔阵中。' % tgt.ui_meta.name

152 else:

153 return '封魔阵没有布置完善，|G【%s】|r侥幸逃了出来！' % tgt.ui_meta.name

154

155

156@ui_meta(definition.FrozenFrogCard)

157 class FrozenFrogCard:

158 # action_stage meta

159 name = '冻青蛙'

160 image = 'thb-card-frozenfrog'

161 tag = 'thb-tag-frozenfrog'

162 description = (

163 '|R冻青蛙|r\n\n'

164 '延时类符卡。\n'

165 '出牌阶段，对一名其他角色使用，将此牌横置于该角色的判定区内。该角色的判定阶段，需进行一次判定然后弃置此牌。若判定结果不为黑桃，跳过其摸牌阶段。\n'

166 '|B|R>> |r判定开始前,你可以使用【好人卡】抵消该符卡的效果（抵消后弃掉【冻青蛙】）。\n\n'

167 '|DB（画师：霏茶，CV：shourei小N）|r'

168 )

169

170 def is_action_valid(self, c, tl):

171 if len(tl) != 1:

172 return (False, '请选择冻青蛙的目标')

173 t = tl[0]

174 if self.me is t:

175 return (False, '你不能跟自己过不去啊！')

176

177 return (True, '伸手党什么的，冻住就好了！')

178

179 def sound_effect(self, act):

180 return 'thb-cv-card_frozenfrog'

181

182

183@ui_meta(spellcard.FrozenFrog)

184 class FrozenFrog:

185 def effect_string(self, act):

186 tgt = act.target

187 if act.succeeded:

188 return '|G【%s】|r被冻住了……' % tgt.ui_meta.name

189 else:

190 return '幻想乡今天大晴，|G【%s】|r没有被冻住~' % tgt.ui_meta.name

191

192

193@ui_meta(definition.NazrinRodCard)

194 class NazrinRodCard:

195 # action_stage meta

196 name = '寻龙尺'

197 image = 'thb-card-nazrinrod'

198 description = (

199 '|R寻龙尺|r\n\n'

200 '非延时符卡。\n'

201 '出牌阶段使用，从牌堆摸两张牌。\n\n'

202 '|DB（画师：霏茶，CV：VV）|r'

203 )

204

205 def is_action_valid(self, c, tl):

206 return (True, '看看能找到什么好东西~')

207

208 def sound_effect(self, act):

209 return 'thb-cv-card_nazrinrod'

210

211

212@ui_meta(definition.SinsackCard)

213 class SinsackCard:

214 # action_stage meta

215 name = '罪袋'

216 image = 'thb-card-sinsack'

217 tag = 'thb-tag-sinsack'

218 description = (

219 '|R罪袋|r\n\n'

220 '延时类符卡。\n'

221 '出牌阶段，对你使用，将此牌横置于你的判定区内。判定区内有此牌的角色的判定阶段，需进行一次判定：\n'

222 '|B|R>> |r若判定结果为黑桃1-8，则目标角色受到3点无来源伤害，然后将其置入弃牌堆。\n'

223 '|B|R>> |r若判定结果不在此范围，则将其移动到下家的判定区内。\n'

224 '|B|R>> |r判定开始前,你可以使用|B好人卡|r抵消该符卡的效果,并将该|G罪袋|r直接传递给下家。\n\n'

225 '|DB（画师：霏茶，CV：VV/大白）|r'

226 )

227

228 def is_action_valid(self, c, tl):

229 return (True, '别来找我！')

230

231 def sound_effect(self, act):

232 return 'thb-cv-card_sinsack'

233

234

235@ui_meta(spellcard.Sinsack)

236 class Sinsack:

237 def effect_string(self, act):

238 tgt = act.target

239 if act.succeeded:

240 return '罪袋终于找到了机会，将|G【%s】|r推倒了…' % tgt.ui_meta.name

241

242

243@ui_meta(spellcard.SinsackDamage)

244 class SinsackDamage:

245 def sound_effect(self, act):

246 return 'thb-cv-card_sinsack_effect'

247

248

249@ui_meta(definition.YukariDimensionCard)

250 class YukariDimensionCard:

251 # action_stage meta

252 image = 'thb-card-yukaridimension'

253 name = '隙间'

254

255 description = (

256 '|R隙间|r\n\n'

257 '非延时符卡。\n'

258 '出牌阶段，对距离为1的一名其他角色使用，获得其区域内的一张牌。\n\n'

259 '|DB（画师：霏茶，CV：VV）|r'

260 )

261

262 def is_action_valid(self, c, tl):

263 if not tl:

264 return (False, '请选择目标')

265

266 target = tl[0]

267 if not (target.cards or target.showncards or target.equips or target.fatetell):

268 return (False, '这货已经没有牌了')

269 else:

270 return (True, '请把胖次给我！')

271

272 def sound_effect(self, act):

273 return 'thb-cv-card_dimension'

274

275

276@ui_meta(spellcard.YukariDimension)

277 class YukariDimension:

278 def effect_string(self, act):

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

280 if act.succeeded:

281 return '|G【%s】|r透过隙间拿走了|G【%s】|r的1张牌。' % (

282 src.ui_meta.name,

283 tgt.ui_meta.name

284 )

285

286

287@ui_meta(definition.DuelCard)

288 class DuelCard:

289 # action_stage meta

290 image = 'thb-card-duel'

291 name = '弹幕战'

292 description = (

293 '|R弹幕战|r\n\n'

294 '非延时符卡。\n'

295 '出牌阶段，对一名其他角色使用，由目标角色开始，轮流打出一张【弹幕】。首先不打出【弹幕】的一方受到另一方造成的1点伤害。\n\n'

296 '|DB（画师：霏茶，CV：小羽）|r'

297 )

298

299 def is_action_valid(self, c, tl):

300 if not tl:

301 return (False, '请选择弹幕战的目标')

302

303 return (True, '来，战个痛快！')

304

305 def sound_effect(self, act):

306 return 'thb-cv-card_duel'

307

308

309@ui_meta(definition.MapCannonCard)

310 class MapCannonCard:

311 image = 'thb-card-mapcannon'

312 name = '地图炮'

313 description = (

314 '|R地图炮|r\n\n'

315 '群体符卡。非延时符卡。\n'

316 '出牌阶段，对除你以外的所有其他角色使用，目标角色需依次打出一张【擦弹】，否则该角色受到1点伤害。\n\n'

317 '|DB（画师：霏茶，CV：VV）|r'

318 )

319

320 def is_action_valid(self, c, tl):

321 return (True, '一个都不能跑！')

322

323 def sound_effect(self, act):

324 return 'thb-cv-card_mapcannon'

325

326

327@ui_meta(definition.DemonParadeCard)

328 class DemonParadeCard:

329 image = 'thb-card-demonparade'

330 name = '百鬼夜行'

331 description = (

332 '|R百鬼夜行|r\n\n'

333 '群体符卡。非延时符卡。\n'

334 '出牌阶段，对除你以外的所有其他角色使用，目标角色需依次打出一张|G弹幕|r，否则该角色受到1点伤害。\n\n'

335 '|DB（画师：霏茶，CV：小羽）|r'

336 )

337

338 def is_action_valid(self, c, tl):

339 return (True, '一只鬼，两只鬼，三只鬼……')

340

341 def sound_effect(self, act):

342 return 'thb-cv-card_demonparade'

343

344

345@ui_meta(definition.FeastCard)

346 class FeastCard:

347 # action_stage meta

348 image = 'thb-card-feast'

349 name = '宴会'

350 description = (

351 '|R宴会|r\n\n'

352 '群体符卡。非延时符卡。\n'

353 '出牌阶段，对所有角色使用。已受伤的角色回复一点体力，未受伤的角色获得|B喝醉|r状态。\n\n'

354 '|DB（画师：霏茶，CV：VV）|r'

355 )

356

357 def is_action_valid(self, c, tl):

358 return (True, '开宴啦~~')

359

360 def sound_effect(self, act):

361 return random.choice([

362 'thb-cv-card_feast1',

363 'thb-cv-card_feast2',

364 'thb-cv-card_feast3',

365 ])

366

367

368@ui_meta(definition.HarvestCard)

369 class HarvestCard:

370 # action_stage meta

371 image = 'thb-card-harvest'

372 name = '五谷丰登'

373 description = (

374 '|R五谷丰登|r\n\n'

375 '群体符卡。非延时符卡。\n'

376 '出牌阶段，对所有角色使用，你从牌堆顶亮出等同于现存角色数量的牌，目标角色依次选择并获得这些牌中的一张。\n\n'

377 '|DB（画师：霏茶，CV：VV）|r'

378 )

379

380 def is_action_valid(self, c, tl):

381 return (True, '麻薯会有的，节操是没有的！')

382

383 def sound_effect(self, act):

384 return 'thb-cv-card_harvest'

385

386

387@ui_meta(spellcard.HarvestEffect)

388 class HarvestEffect:

389 def effect_string(self, act):

390 if not act.succeeded: return None

391 tgt = act.target

392 c = act.card

393 return '|G【%s】|r获得了|G%s|r。' % (

394 tgt.ui_meta.name,

395 self.card_desc(c),

396 )

397

398

399@ui_meta(definition.DollControlCard)

400 class DollControlCard:

401 # action_stage meta

402 name = '人形操控'

403 image = 'thb-card-dollcontrol'

404 description = (

405 '|R人形操控|r\n\n'

406 '非延时符卡。\n'

407 '出牌阶段，对装备区内有武器牌的一名其他角色使用，令其选择一项：对其攻击范围内一名由你指定的角色使用一张【弹幕】，或将武器交给你。\n\n'

408 '|DB（画师：霏茶，CV：小羽）|r'

409 )

410 custom_ray = True

411

412 def is_action_valid(self, c, tl):

413 n = len(tl)

414 if n == 0:

415 return (False, '请选择被控者')

416

417 if tl[0] is self.me:

418 return (False, '你不可以控制你自己')

419

420 if all(e.equipment_category != 'weapon' for e in tl[0].equips):

421 return (False, '被控者没有武器！')

422

423 if n == 1:

424 return (False, '请选择被控者的攻击目标')

425 elif n == 2:

426 from thb.actions import LaunchCard

427 from thb.cards.definition import AttackCard

428 c = AttackCard()

429 lc = LaunchCard(tl[0], [tl[1]], c)

430 if not lc.can_fire():

431 return (False, '被控者无法向目标出【弹幕】！')

432 return (True, '乖，听话！')

433

434 def sound_effect(self, act):

435 return 'thb-cv-card_dollcontrol'

436

437

438@ui_meta(spellcard.DollControl)

439 class DollControl:

440 # choose card meta

441 def choose_card_text(self, act, cards):

442 if act.cond(cards):

443 return (True, '那好吧…')

444 else:

445 return (False, '请出【弹幕】（否则你的武器会被拿走）')

446

447 def ray(self, act):

448 src = act.source

449 tl = act.target_list

450 return [(src, tl[0]), (tl[0], tl[1])]

451

452

453@ui_meta(definition.DonationBoxCard)

454 class DonationBoxCard:

455 # action_stage meta

456 name = '赛钱箱'

457 image = 'thb-card-donationbox'

458 description = (

459 '|R赛钱箱|r\n\n'

460 '非延时符卡。\n'

461 '出牌阶段，对至多两名其他角色使用，目标角色需将自己的一张牌置入你的明牌区。\n\n'

462 '|DB（画师：霏茶，CV：shourei小N）|r'

463 )

464

465 def is_action_valid(self, c, tl):

466 n = len(tl)

467 if not n:

468 return (False, '请选择1-2名玩家')

469

470 for t in tl:

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

472 return (False, '目标没有可以给你的牌')

473

474 return (True, '纳奉！纳奉！')

475

476 def sound_effect(self, act):

477 return 'thb-cv-card_donationbox'

478

479

480@ui_meta(spellcard.DonationBoxEffect)

481 class DonationBoxEffect:

482 # choose card meta

483 def choose_card_text(self, act, cards):

484 if act.cond(cards):

485 return (True, '这是抢劫啊！')

486 else:

487 return (False, '请选择一张牌（否则会随机选择一张）')