# Coverage for thb/characters/meirin.py : 35%

## 88 statements 39 run 49 missing 0 excluded 1 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# -- third party --

6# -- own --

7 from thb.actions import DropCards, GenericAction, MaxLifeChange, PrepareStage, random_choose_card

8 from thb.cards.base import DummyCard, Skill, t_None

9 from thb.cards.classes import AttackCard, BaseAttack, GrazeCard, LaunchGraze, TreatAs

10 from thb.characters.base import Character, register_character_to

11 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

12 from thb.mode import THBEventHandler

13

14

15# -- code --

16 class LoongPunch(Skill):

17 associated_action = None

18 skill_category = ['character', 'passive']

19 target = t_None()

20

21

22 class Taichi(TreatAs, Skill):

23 skill_category = ['character', 'active']

24

25 @property

26 def treat_as(self):

27 cl = self.associated_cards

28 if not cl: return DummyCard

29 c = cl[0]

30 if c.is_card(GrazeCard):

31 return AttackCard

32 if c.is_card(AttackCard):

33 return GrazeCard

34 return DummyCard

35

36 def check(self):

37 cl = self.associated_cards

38 if not cl or len(cl) != 1: return False

39 c = cl[0]

40 if not (c.is_card(AttackCard) or c.is_card(GrazeCard)): return False

41 return c.resides_in is not None and c.resides_in.type in (

42 'cards', 'showncards',

43 )

44

45

46 class RiverBehind(Skill):

47 associated_action = None

48 skill_category = ['character', 'passive', 'awake']

49 target = t_None()

50

51

52 class LoongPunchAction(GenericAction):

53 def __init__(self, source, target, _type):

54 self.source = source

55 self.target = target

56 self.type = _type

57

58 def apply_action(self):

59 g = self.game

60 src, tgt = self.source, self.target

61 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards')))

62 c = c or random_choose_card(g, [tgt.cards, tgt.showncards])

63 if not c: return False

64 g.players.exclude(tgt).reveal(c)

65 g.process_action(DropCards(src, tgt, [c]))

66 return True

67

68

69 class LoongPunchHandler(THBEventHandler):

70 interested = ['action_after']

71 execute_after = ['DeathSickleHandler']

72

73 def handle(self, evt_type, act):

74 if evt_type == 'action_after' and isinstance(act, LaunchGraze):

75 if not act.succeeded: return act

76 g = self.game

77 pact = g.action_stack[-1]

78 if not isinstance(pact, BaseAttack): return act

79 self.do_effect(pact.source, pact.target, 'attack')

80 self.do_effect(pact.target, pact.source, 'graze')

81

82 return act

83

84 def do_effect(self, src, tgt, _type):

85 if not src.has_skill(LoongPunch): return

86 if not (tgt.cards or tgt.showncards): return

87 g = self.game

88 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))): return

89

90 g.process_action(LoongPunchAction(src, tgt, _type))

91

92

93 class RiverBehindAwake(GenericAction):

94 def apply_action(self):

95 tgt = self.target

96 assert tgt.has_skill(RiverBehind)

97 tgt.skills.remove(RiverBehind)

98 tgt.skills.append(Taichi)

99 g = self.game

100 g.process_action(MaxLifeChange(tgt, tgt, -1))

101 return True

102

103

104 class RiverBehindHandler(THBEventHandler):

105 interested = ['action_apply']

106

107 def handle(self, evt_type, act):

108 if evt_type == 'action_apply' and isinstance(act, PrepareStage): 108 ↛ 109 line 108 didn't jump to line 109, because the condition on line 108 was never true

109 tgt = act.target

110 if not tgt.has_skill(RiverBehind): return act

111 g = self.game

112 if tgt.life <= 2 and tgt.life <= min(p.life for p in g.players if not p.dead):

113 g.process_action(RiverBehindAwake(tgt, tgt))

114 return act

115

116

117@register_character_to('common')

118 class Meirin(Character):

119 skills = [LoongPunch, RiverBehind]

120 eventhandlers = [RiverBehindHandler, LoongPunchHandler]

121 maxlife = 4