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 enum import IntEnum 

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

7import random 

8 

9# -- third party -- 

10from typing_extensions import TypedDict 

11 

12# -- own -- 

13from thb import actions 

14from thb.meta.common import ui_meta 

15from utils.misc import BatchList 

16 

17# -- typing -- 

18if TYPE_CHECKING: 

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

20 

21 

22# -- code -- 

23@ui_meta(actions.DrawCards) 

24class 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 

36class 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) 

49class 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) 

65class 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) 

85class 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) 

96class 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) 

136class 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) 

151class 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) 

170class 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) 

181class 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) 

191class 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) 

227class 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) 

249class 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) 

281class ActionShootdown: 

282 target_independent = False 

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

284 

285 

286@ui_meta(actions.BaseActionStage) 

287class 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) 

298class VitalityLimitExceeded: 

299 target_independent = True 

300 shootdown_message = '你没有干劲了' 

301 

302 

303class 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 

329class CardView(TypedDict): 

330 card: str 

331 suit: int 

332 number: int 

333 sync_id: int 

334 

335 

336@ui_meta(actions.MigrateCardsTransaction) 

337class 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