Coverage for thb/meta/cards/spellcard.py : 48%
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
4# -- stdlib --
5import itertools
6import random
8# -- third party --
9# -- own --
10from thb.cards import definition, spellcard
11from thb.cards.base import PhysicalCard
12from thb.meta.common import ui_meta
15# -- code --
16@ui_meta(definition.DemolitionCard)
17class 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 )
28 def is_action_valid(self, c, tl):
29 if not tl:
30 return (False, '请选择拆除目标')
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, '嗯,你的牌太多了')
38 def sound_effect(self, act):
39 return 'thb-cv-card_demolition'
42@ui_meta(spellcard.Demolition)
43class Demolition:
44 def effect_string(self, act):
45 if not act.succeeded: return None
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
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 )
63@ui_meta(definition.RejectCard)
64class 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 )
75 def is_action_valid(self, c, tl):
76 return (False, '你不能主动出好人卡')
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 )
86 def sound_effect(self, act):
87 return 'thb-cv-card_reject'
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
94 from thb.characters import reimu
95 if p.has_skill(reimu.SpiritualAttack) and not p.dead:
96 return True
98 return False
101@ui_meta(spellcard.RejectHandler)
102class 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
108 s = '【%s】受到的【%s】' % (
109 act.target_act.target.ui_meta.name,
110 name,
111 )
113 if act.cond(cards):
114 return (True, '对不起,你是一个好人(%s)' % s)
115 else:
116 return (False, '请选择一张好人卡(%s)' % s)
119@ui_meta(definition.SealingArrayCard)
120class 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 )
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, '你不能跟自己过不去啊!')
140 return (True, '画个圈圈诅咒你!')
142 def sound_effect(self, act):
143 return 'thb-cv-card_sealarray'
146@ui_meta(spellcard.SealingArray)
147class 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
156@ui_meta(definition.FrozenFrogCard)
157class 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 )
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, '你不能跟自己过不去啊!')
177 return (True, '伸手党什么的,冻住就好了!')
179 def sound_effect(self, act):
180 return 'thb-cv-card_frozenfrog'
183@ui_meta(spellcard.FrozenFrog)
184class 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
193@ui_meta(definition.NazrinRodCard)
194class 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 )
205 def is_action_valid(self, c, tl):
206 return (True, '看看能找到什么好东西~')
208 def sound_effect(self, act):
209 return 'thb-cv-card_nazrinrod'
212@ui_meta(definition.SinsackCard)
213class 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 )
228 def is_action_valid(self, c, tl):
229 return (True, '别来找我!')
231 def sound_effect(self, act):
232 return 'thb-cv-card_sinsack'
235@ui_meta(spellcard.Sinsack)
236class Sinsack:
237 def effect_string(self, act):
238 tgt = act.target
239 if act.succeeded:
240 return '罪袋终于找到了机会,将|G【%s】|r推倒了…' % tgt.ui_meta.name
243@ui_meta(spellcard.SinsackDamage)
244class SinsackDamage:
245 def sound_effect(self, act):
246 return 'thb-cv-card_sinsack_effect'
249@ui_meta(definition.YukariDimensionCard)
250class YukariDimensionCard:
251 # action_stage meta
252 image = 'thb-card-yukaridimension'
253 name = '隙间'
255 description = (
256 '|R隙间|r\n\n'
257 '非延时符卡。\n'
258 '出牌阶段,对距离为1的一名其他角色使用,获得其区域内的一张牌。\n\n'
259 '|DB(画师:霏茶,CV:VV)|r'
260 )
262 def is_action_valid(self, c, tl):
263 if not tl:
264 return (False, '请选择目标')
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, '请把胖次给我!')
272 def sound_effect(self, act):
273 return 'thb-cv-card_dimension'
276@ui_meta(spellcard.YukariDimension)
277class 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 )
287@ui_meta(definition.DuelCard)
288class 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 )
299 def is_action_valid(self, c, tl):
300 if not tl:
301 return (False, '请选择弹幕战的目标')
303 return (True, '来,战个痛快!')
305 def sound_effect(self, act):
306 return 'thb-cv-card_duel'
309@ui_meta(definition.MapCannonCard)
310class 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 )
320 def is_action_valid(self, c, tl):
321 return (True, '一个都不能跑!')
323 def sound_effect(self, act):
324 return 'thb-cv-card_mapcannon'
327@ui_meta(definition.DemonParadeCard)
328class 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 )
338 def is_action_valid(self, c, tl):
339 return (True, '一只鬼,两只鬼,三只鬼……')
341 def sound_effect(self, act):
342 return 'thb-cv-card_demonparade'
345@ui_meta(definition.FeastCard)
346class 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 )
357 def is_action_valid(self, c, tl):
358 return (True, '开宴啦~~')
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 ])
368@ui_meta(definition.HarvestCard)
369class 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 )
380 def is_action_valid(self, c, tl):
381 return (True, '麻薯会有的,节操是没有的!')
383 def sound_effect(self, act):
384 return 'thb-cv-card_harvest'
387@ui_meta(spellcard.HarvestEffect)
388class 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 )
399@ui_meta(definition.DollControlCard)
400class 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
412 def is_action_valid(self, c, tl):
413 n = len(tl)
414 if n == 0:
415 return (False, '请选择被控者')
417 if tl[0] is self.me:
418 return (False, '你不可以控制你自己')
420 if all(e.equipment_category != 'weapon' for e in tl[0].equips):
421 return (False, '被控者没有武器!')
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, '乖,听话!')
434 def sound_effect(self, act):
435 return 'thb-cv-card_dollcontrol'
438@ui_meta(spellcard.DollControl)
439class 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, '请出【弹幕】(否则你的武器会被拿走)')
447 def ray(self, act):
448 src = act.source
449 tl = act.target_list
450 return [(src, tl[0]), (tl[0], tl[1])]
453@ui_meta(definition.DonationBoxCard)
454class 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 )
465 def is_action_valid(self, c, tl):
466 n = len(tl)
467 if not n:
468 return (False, '请选择1-2名玩家')
470 for t in tl:
471 if not (t.cards or t.showncards or t.equips):
472 return (False, '目标没有可以给你的牌')
474 return (True, '纳奉!纳奉!')
476 def sound_effect(self, act):
477 return 'thb-cv-card_donationbox'
480@ui_meta(spellcard.DonationBoxEffect)
481class 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, '请选择一张牌(否则会随机选择一张)')