Coverage for thb/meta/actions.py : 35%
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 --
5from enum import IntEnum
6from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union
7import random
9# -- third party --
10from typing_extensions import TypedDict
12# -- own --
13from thb import actions
14from thb.meta.common import ui_meta
15from utils.misc import BatchList
17# -- typing --
18if TYPE_CHECKING:
19 from thb.cards.base import Card, CardList # noqa: F401
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''
31 return u'|G【%s】|r%s摸了%d张牌。' % (
32 act.target.ui_meta.name, direction, act.amount,
33 )
36class PutBack:
37 def effect_string(self, act):
38 if act.direction == -1:
39 direction = u'底'
40 else:
41 direction = u''
43 return u'|G【%s】|r将%d张牌放回了牌堆%s。' % (
44 act.target.ui_meta.name, len(act.cards), direction,
45 )
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)
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 )
64@ui_meta(actions.Damage)
65class Damage:
66 update_portrait = True
67 play_sound_at_target = True
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 )
80 def sound_effect(self, act):
81 return 'thb-sound-hit'
84@ui_meta(actions.LifeLost)
85class LifeLost:
86 update_portrait = True
87 play_sound_at_target = True
89 def effect_string(self, act):
90 return '|G【%s】|r流失了%d点体力。' % (
91 act.target.ui_meta.name, act.amount
92 )
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
103 es = None
105 meta = getattr(c, 'ui_meta', None)
106 effect_string = getattr(meta, 'effect_string', None)
107 if effect_string:
108 es = effect_string(act)
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 )
116 return s
118 def sound_effect_before(self, act):
119 c = act.card
120 if not c:
121 return
123 meta = getattr(c, 'ui_meta', None)
124 se = getattr(meta, 'sound_effect', None)
125 return se and se(act)
127 def ray(self, act):
128 if getattr(act.card.ui_meta, 'custom_ray', False):
129 return []
131 s = act.source
132 return [(s, t) for t in act.target_list]
135@ui_meta(actions.AskForCard)
136class AskForCard:
137 def sound_effect_after(self, act):
138 c = act.card
139 if not c:
140 return
142 if act.card_usage != 'use':
143 return
145 meta = getattr(c, 'ui_meta', None)
146 se = getattr(meta, 'sound_effect', None)
147 return se and se(act)
150@ui_meta(actions.PlayerDeath)
151class PlayerDeath:
152 update_portrait = True
154 def effect_string(self, act):
155 tgt = act.target
156 return '|G【%s】|r被击坠了。' % (
157 tgt.ui_meta.name,
158 )
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
169@ui_meta(actions.PlayerRevive)
170class PlayerRevive:
171 update_portrait = True
173 def effect_string(self, act):
174 tgt = act.target
175 return '|G【%s】|r重新回到了场上。' % (
176 tgt.ui_meta.name,
177 )
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 )
190@ui_meta(actions.RevealRole)
191class RevealRole:
192 def effect_string(self, act: actions.RevealRole):
193 from thb.characters.base import Character
195 g: Any = self.game
196 me = self.me
198 if isinstance(me, Character):
199 p = me.player
200 else:
201 p = me
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
211 p = act.player
212 role = act.role
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'
220 return '%s的身份是:|R%s|r。' % (
221 name,
222 g.ui_meta.roles_disp[role.get()],
223 )
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, '请选择一张牌用于拼点')
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 )
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 )
248@ui_meta(actions.Fatetell)
249class Fatetell:
251 def fatetell_prompt_string(self, act):
252 act_name = None
254 try:
255 card = act.initiator.associated_card
256 act_name = card.ui_meta.name
257 except AttributeError:
258 pass
260 try:
261 act_name = act.initiator.ui_meta.fatetell_display_name
262 except AttributeError:
263 pass
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 )
277 return prompt
280@ui_meta(actions.ActionShootdown)
281class ActionShootdown:
282 target_independent = False
283 shootdown_message = '您不能这样出牌'
286@ui_meta(actions.BaseActionStage)
287class BaseActionStage:
288 idle_prompt = '请出牌…'
290 def choose_card_text(self, act, cards):
291 if not act.cond(cards):
292 return False, '您选择的牌不符合出牌规则'
293 else:
294 return True, '不会显示'
297@ui_meta(actions.VitalityLimitExceeded)
298class VitalityLimitExceeded:
299 target_independent = True
300 shootdown_message = '你没有干劲了'
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
329class CardView(TypedDict):
330 card: str
331 suit: int
332 number: int
333 sync_id: int
336@ui_meta(actions.MigrateCardsTransaction)
337class MigrateCardsTransaction:
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))
347 # from UnityEngine import Debug
348 # Debug.Log(repr(rst))
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
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 }
368 def animation_instructions(self, trans: actions.MigrateCardsTransaction) -> List[Union[MigrateCardsAnimationOp, CardView]]:
369 Op = MigrateCardsAnimationOp
371 me = self.me
372 ops: List[Union[MigrateCardsAnimationOp, CardView]] = []
374 # class CardMovement:
375 # trans: MigrateCardsTransaction
376 # card: Card
377 # fr: CardList
378 # to: CardList
379 # direction: Literal['front', 'back'] = 'front'
381 for m in trans.movements:
382 # -- card actions --
383 c = self.card_view(m.card)
384 tail: List[Union[MigrateCardsAnimationOp, CardView]] = []
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]
393 showing = (m.fr.type == 'showncards', m.to.type == 'showncards')
395 if showing == (True, False):
396 tail += [Op.DUP, Op.UNSHOW]
397 elif showing == (False, True):
398 tail += [Op.DUP, Op.SHOW]
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]
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
412 ops += tail
414 return ops
416 def detach_animation_instructions(self, trans: actions.MigrateCardsTransaction, cards: Sequence[Card]) -> List[Union[MigrateCardsAnimationOp, CardView]]:
417 Op = MigrateCardsAnimationOp
419 me = self.me
420 ops: List[Union[MigrateCardsAnimationOp, CardView]] = []
422 for c in cards:
423 cv = self.card_view(c)
424 fr = c.resides_in
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]
433 if fr.type == 'showncards':
434 ops += [Op.DUP, Op.UNSHOW]
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]
442 return ops