# Coverage for thb/meta/actions.py : 35%

## 244 statements 90 run 154 missing 2 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 from enum import IntEnum

6 from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union

7 import random

8

9# -- third party --

10 from typing_extensions import TypedDict

11

12# -- own --

13 from thb import actions

14 from thb.meta.common import ui_meta

15 from utils.misc import BatchList

16

17# -- typing --

18 if TYPE_CHECKING:

19 from thb.cards.base import Card, CardList # noqa: F401

20

21

22# -- code --

23@ui_meta(actions.DrawCards)

24 class DrawCards:

25 def effect_string(self, act):

26 if act.back:

27 direction = u'从牌堆底'

28 else:

29 direction = u''

30

31 return u'|G【%s】|r%s摸了%d张牌。' % (

32 act.target.ui_meta.name, direction, act.amount,

33 )

34

35

36 class PutBack:

37 def effect_string(self, act):

38 if act.direction == -1:

39 direction = u'底'

40 else:

41 direction = u''

42

43 return u'|G【%s】|r将%d张牌放回了牌堆%s。' % (

44 act.target.ui_meta.name, len(act.cards), direction,

45 )

46

47

48@ui_meta(actions.ActiveDropCards)

49 class ActiveDropCards:

50 # choose_card meta

51 def choose_card_text(self, act, cards):

52 if act.cond(cards):

53 return (True, 'OK，就这些了')

54 else:

55 return (False, '请弃掉%d张牌…' % act.dropn)

56

57 def effect_string(self, act):

58 if act.dropn > 0 and act.cards:

59 return '|G【%s】|r弃掉了%d张牌：%s。' % (

60 act.target.ui_meta.name, act.dropn, self.card_desc(act.cards),

61 )

62

63

64@ui_meta(actions.Damage)

65 class Damage:

66 update_portrait = True

67 play_sound_at_target = True

68

69 def effect_string(self, act):

70 s, t = act.source, act.target

71 if s:

72 return '|G【%s】|r对|G【%s】|r造成了%d点伤害。' % (

73 s.ui_meta.name, t.ui_meta.name, act.amount

74 )

75 else:

76 return '|G【%s】|r受到了%d点无来源的伤害。' % (

77 t.ui_meta.name, act.amount

78 )

79

80 def sound_effect(self, act):

81 return 'thb-sound-hit'

82

83

84@ui_meta(actions.LifeLost)

85 class LifeLost:

86 update_portrait = True

87 play_sound_at_target = True

88

89 def effect_string(self, act):

90 return '|G【%s】|r流失了%d点体力。' % (

91 act.target.ui_meta.name, act.amount

92 )

93

94

95@ui_meta(actions.LaunchCard)

96 class LaunchCard:

97 def effect_string_before(self, act: actions.LaunchCard) -> Optional[str]:

98 s, tl = act.source, BatchList(act.target_list)

99 c = act.card

100 if not c:

101 return None

102

103 es = None

104

105 meta = getattr(c, 'ui_meta', None)

106 effect_string = getattr(meta, 'effect_string', None)

107 if effect_string:

108 es = effect_string(act)

109

110 s = es or '|G【%s】|r对|G【%s】|r使用了|G%s|r。' % (

111 s.ui_meta.name,

112 '】|r、|G【'.join(tl.ui_meta.name),

113 act.card.ui_meta.name

114 )

115

116 return s

117

118 def sound_effect_before(self, act):

119 c = act.card

120 if not c:

121 return

122

123 meta = getattr(c, 'ui_meta', None)

124 se = getattr(meta, 'sound_effect', None)

125 return se and se(act)

126

127 def ray(self, act):

128 if getattr(act.card.ui_meta, 'custom_ray', False):

129 return []

130

131 s = act.source

132 return [(s, t) for t in act.target_list]

133

134

135@ui_meta(actions.AskForCard)

136 class AskForCard:

137 def sound_effect_after(self, act):

138 c = act.card

139 if not c:

140 return

141

142 if act.card_usage != 'use':

143 return

144

145 meta = getattr(c, 'ui_meta', None)

146 se = getattr(meta, 'sound_effect', None)

147 return se and se(act)

148

149

150@ui_meta(actions.PlayerDeath)

151 class PlayerDeath:

152 update_portrait = True

153

154 def effect_string(self, act):

155 tgt = act.target

156 return '|G【%s】|r被击坠了。' % (

157 tgt.ui_meta.name,

158 )

159

160 def sound_effect(self, act):

161 meta = act.target.ui_meta

162 se = getattr(meta, 'miss_sound_effect', None)

163 if isinstance(se, (list, tuple)):

164 return random.choice(se)

165 else:

166 return se

167

168

169@ui_meta(actions.PlayerRevive)

170 class PlayerRevive:

171 update_portrait = True

172

173 def effect_string(self, act):

174 tgt = act.target

175 return '|G【%s】|r重新回到了场上。' % (

176 tgt.ui_meta.name,

177 )

178

179

180@ui_meta(actions.TurnOverCard)

181 class TurnOverCard:

182 def effect_string(self, act):

183 tgt = act.target

184 return '|G【%s】|r翻开了牌堆顶的一张牌，%s。' % (

185 tgt.ui_meta.name,

186 self.card_desc(act.card)

187 )

188

189

190@ui_meta(actions.RevealRole)

191 class RevealRole:

192 def effect_string(self, act: actions.RevealRole):

193 from thb.characters.base import Character

194

195 g: Any = self.game

196 me = self.me

197

198 if isinstance(me, Character):

199 p = me.player

200 else:

201 p = me

202

203 while True:

204 if p is act.to:

205 break

206 if isinstance(act.to, list):

207 if p in act.to:

208 break

209 return

210

211 p = act.player

212 role = act.role

213

214 try:

215 ch = g.find_character(p)

216 name = f'|G{ch.ui_meta.name}|r'

217 except Exception:

218 name = f'|R{p.name}|r'

219

220 return '%s的身份是：|R%s|r。' % (

221 name,

222 g.ui_meta.roles_disp[role.get()],

223 )

224

225

226@ui_meta(actions.Pindian)

227 class Pindian:

228 # choose_card meta

229 def choose_card_text(self, act, cards):

230 if act.cond(cards):

231 return (True, '不服来战！')

232 else:

233 return (False, '请选择一张牌用于拼点')

234

235 def effect_string_before(self, act):

236 return '|G【%s】|r对|G【%s】|r发起了拼点：' % (

237 act.source.ui_meta.name,

238 act.target.ui_meta.name,

239 )

240

241 def effect_string(self, act):

242 winner = act.source if act.succeeded else act.target

243 return '|G【%s】|r是人生赢家！' % (

244 winner.ui_meta.name

245 )

246

247

248@ui_meta(actions.Fatetell)

249 class Fatetell:

250

251 def fatetell_prompt_string(self, act):

252 act_name = None

253

254 try:

255 card = act.initiator.associated_card

256 act_name = card.ui_meta.name

257 except AttributeError:

258 pass

259

260 try:

261 act_name = act.initiator.ui_meta.fatetell_display_name

262 except AttributeError:

263 pass

264

265 if act_name:

266 prompt = '|G【%s】|r进行了一次判定（|G%s|r），结果为%s。' % (

267 act.target.ui_meta.name,

268 act_name,

269 self.card_desc(act.card)

270 )

271 else:

272 prompt = '|G【%s】|r进行了一次判定，结果为%s。' % (

273 act.target.ui_meta.name,

274 self.card_desc(act.card)

275 )

276

277 return prompt

278

279

280@ui_meta(actions.ActionShootdown)

281 class ActionShootdown:

282 target_independent = False

283 shootdown_message = '您不能这样出牌'

284

285

286@ui_meta(actions.BaseActionStage)

287 class BaseActionStage:

288 idle_prompt = '请出牌…'

289

290 def choose_card_text(self, act, cards):

291 if not act.cond(cards):

292 return False, '您选择的牌不符合出牌规则'

293 else:

294 return True, '不会显示'

295

296

297@ui_meta(actions.VitalityLimitExceeded)

298 class VitalityLimitExceeded:

299 target_independent = True

300 shootdown_message = '你没有干劲了'

301

302

303 class MigrateCardsAnimationOp(IntEnum):

304 PUSH = 0 # Push next as-is

305 DUP = 1 # Duplicate top elem

306 GET = 2 # Get a CardSprite, find in hand/dropped area. Create if not found. args: Card, create_in return value: CardSprite.

307 MOVE = 3 # Move CardSprite to another Area. arg: CardSprite, Area, no return value.

308 FADE = 4 # Fade CardSprite, arg: CardSprite, no ret val.

309 GRAY = 5 # Set CardSprite gray, arg: CardSprite, no ret val.

310 UNGRAY = 6 # Unset CardSprite gray, arg: CardSprite, no ret val.

311 FATETELL = 7 # Play Fatetell animation, arg: CardSprite, no ret val.

312 SHOW = 8

313 UNSHOW = 9

314 AREA_HAND = 10

315 AREA_DECK = 11

316 AREA_DROP = 12

317 AREA_PORT0 = 13

318 AREA_PORT1 = 14

319 AREA_PORT2 = 15

320 AREA_PORT3 = 16

321 AREA_PORT4 = 17

322 AREA_PORT5 = 18

323 AREA_PORT6 = 19

324 AREA_PORT7 = 20

325 AREA_PORT8 = 21

326 AREA_PORT9 = 22

327

328

329 class CardView(TypedDict):

330 card: str

331 suit: int

332 number: int

333 sync_id: int

334

335

336@ui_meta(actions.MigrateCardsTransaction)

337 class MigrateCardsTransaction:

338

339 # def dbgprint(self, ins):

340 # rst: Any = []

341 # for i in ins:

342 # if isinstance(i, int):

343 # rst.append(Op(i))

344 # else:

345 # rst.append(repr(i))

346

347 # from UnityEngine import Debug

348 # Debug.Log(repr(rst))

349

350 def cl2index(self, cl: CardList) -> int:

351 g = self.game

352 assert cl.owner

353 for i, p in enumerate(g.players):

354 if p.player == cl.owner.player:

355 return i

356 else:

357 raise ValueError

358

359 @staticmethod

360 def card_view(c: Card) -> CardView:

361 return {

362 'card': c.__class__.__name__,

363 'suit': c.suit,

364 'number': c.number,

365 'sync_id': c.sync_id,

366 }

367

368 def animation_instructions(self, trans: actions.MigrateCardsTransaction) -> List[Union[MigrateCardsAnimationOp, CardView]]:

369 Op = MigrateCardsAnimationOp

370

371 me = self.me

372 ops: List[Union[MigrateCardsAnimationOp, CardView]] = []

373

374 # class CardMovement:

375 # trans: MigrateCardsTransaction

376 # card: Card

377 # fr: CardList

378 # to: CardList

379 # direction: Literal['front', 'back'] = 'front'

380

381 for m in trans.movements:

382 # -- card actions --

383 c = self.card_view(m.card)

384 tail: List[Union[MigrateCardsAnimationOp, CardView]] = []

385

386 if m.fr.type in ('deckcard', 'droppedcard') or not m.fr.owner:

387 tail += [c, Op.AREA_DECK, Op.GET]

388 elif m.fr in (me.cards, me.showncards):

389 tail += [c, Op.AREA_HAND, Op.GET]

390 else:

391 tail += [c, Op(Op.AREA_PORT0 + self.cl2index(m.fr)), Op.GET]

392

393 showing = (m.fr.type == 'showncards', m.to.type == 'showncards')

394

395 if showing == (True, False):

396 tail += [Op.DUP, Op.UNSHOW]

397 elif showing == (False, True):

398 tail += [Op.DUP, Op.SHOW]

399

400 if m.to in (me.cards, me.showncards):

401 tail += [Op.DUP, Op.UNGRAY, Op.AREA_HAND, Op.MOVE]

402 else:

403 if m.to.type in ('droppedcard', 'deckcard'):

404 gray = m.to.type == 'droppedcard'

405 tail += [Op.DUP, Op.GRAY if gray else Op.UNGRAY, Op.AREA_DROP, Op.MOVE]

406

407 elif m.to.owner:

408 tail += [Op.DUP, Op.UNGRAY, Op.DUP, Op.FADE, Op(Op.AREA_PORT0 + self.cl2index(m.to)), Op.MOVE]

409 else:

410 continue # no animation

411

412 ops += tail

413

414 return ops

415

416 def detach_animation_instructions(self, trans: actions.MigrateCardsTransaction, cards: Sequence[Card]) -> List[Union[MigrateCardsAnimationOp, CardView]]:

417 Op = MigrateCardsAnimationOp

418

419 me = self.me

420 ops: List[Union[MigrateCardsAnimationOp, CardView]] = []

421

422 for c in cards:

423 cv = self.card_view(c)

424 fr = c.resides_in

425

426 if fr.type in ('deckcard', 'droppedcard') or not fr.owner:

427 ops += [cv, Op.AREA_DECK, Op.GET]

428 elif fr in (me.cards, me.showncards):

429 ops += [cv, Op.AREA_HAND, Op.GET]

430 else:

431 ops += [cv, Op(Op.AREA_PORT0 + self.cl2index(fr)), Op.GET]

432

433 if fr.type == 'showncards':

434 ops += [Op.DUP, Op.UNSHOW]

435

436 act = trans.action

437 if isinstance(act, actions.BaseFatetell):

438 ops += [Op.DUP, Op.DUP, Op.UNGRAY if act.succeeded else Op.GRAY, Op.FATETELL, Op.AREA_DROP, Op.MOVE]

439 else:

440 ops += [Op.DUP, Op.UNGRAY, Op.AREA_DROP, Op.MOVE]

441

442 return ops