# Coverage for thb/characters/chen.py : 95%

## 109 statements 107 run 2 missing 0 excluded 6 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 DrawCards, ForEach, LaunchCard, PlayerTurn, UserAction

8 from thb.cards.base import Skill

9 from thb.cards.classes import AttackCard, DollControlCard, Heal, InstantSpellCardAction, RejectCard

10 from thb.cards.classes import t_OtherOne

11 from thb.characters.base import Character, register_character_to

12 from thb.inputlets import ChooseOptionInputlet

13 from thb.mode import THBEventHandler

14

15

16# -- code --

17 class FlyingSkandaAction(ForEach):

18

19 def prepare(self):

20 skill = self.associated_card

21 card = skill.associated_cards[0]

22 action = card.associated_action

23 self.action_cls = action

24

25 def is_valid(self):

26 p = self.source

27 if p.tags['turn_count'] <= p.tags['flying_skanda']:

28 return False

29 if any(t.dead for t in self.target_list): 29 ↛ 30 line 29 didn't jump to line 30, because the condition on line 29 was never true

30 return False

31 return True

32

33 def get_actual_action(self, act):

34 return None

35

36

37 class FlyingSkanda(Skill):

38 associated_action = FlyingSkandaAction

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

40 usage = 'launch'

41

42 def target(self, src, tl):

43 tl = [ch for ch in tl if not ch.dead]

44 cl = self.associated_cards

45 if not cl: return ([], False)

46 c = cl[0]

47 if len(tl) < 2:

48 return c.target(src, tl)

49 else:

50 rst = c.target(src, tl[:-1])

51 a = tl[-1]

52 if a is src:

53 return rst[0], False

54 else:

55 return rst[0] + [a], rst[1]

56

57 @property

58 def distance(self):

59 cl = self.associated_cards

60 if not cl: return 0 60 ↛ exit line 60 didn't return from function 'distance', because the return on line 60 wasn't executed

61 return cl[0].distance

62

63 def check(self):

64 cl = self.associated_cards

65 if len(cl) != 1: return False

66 c = cl[0]

67 if c.is_card(AttackCard): return True

68

69 if c.is_card(DollControlCard): return False 69 ↛ exit line 69 didn't return from function 'check', because the return on line 69 wasn't executed

70 if c.is_card(RejectCard): return False 70 ↛ exit line 70 didn't return from function 'check', because the return on line 70 wasn't executed

71

72 act = c.associated_action

73 if not act: return False 73 ↛ exit line 73 didn't return from function 'check', because the return on line 73 wasn't executed

74 if not issubclass(act, InstantSpellCardAction): return False

75

76 return True

77

78 def is_card(self, cls):

79 cl = self.associated_cards

80 if cl and cl[0].is_card(cls): return True

81 return isinstance(self, cls)

82

83

84 class FlyingSkandaHandler(THBEventHandler):

85 interested = ['action_after']

86

87 def handle(self, evt_type, act):

88 if evt_type == 'action_after' and isinstance(act, LaunchCard):

89 if not act.card.is_card(FlyingSkanda): return act

90 act.source.tags['flying_skanda'] = act.source.tags['turn_count']

91 return act

92

93

94 class ShikigamiAction(UserAction):

95 def apply_action(self):

96 tgt = self.target

97 src = self.source

98

99 g = self.game

100

101 if tgt.life < tgt.maxlife and g.user_input(

102 [tgt], ChooseOptionInputlet(self, (False, True))

103 ):

104 g.process_action(Heal(src, tgt))

105 else:

106 g.process_action(DrawCards(tgt, 2))

107

108 tgt.tags['shikigami_target'] = src

109 src.tags['shikigami_target'] = tgt

110 src.tags['shikigami_tag'] = src.tags['turn_count']

111

112 return True

113

114 def is_valid(self):

115 return 'shikigami_tag' not in self.source.tags

116

117

118 class Shikigami(Skill):

119 associated_action = ShikigamiAction

120 skill_category = ['character', 'active', 'once']

121 target = t_OtherOne()

122

123 def check(self):

124 return not self.associated_cards

125

126

127 class ShikigamiHandler(THBEventHandler):

128 interested = ['post_calcdistance']

129

130 def handle(self, evt_type, arg):

131 if evt_type == 'post_calcdistance': 131 ↛ 152 line 131 didn't jump to line 152, because the condition on line 131 was never false

132 src, card, dist = arg

133 if not card.is_card(AttackCard): return arg

134

135 tgt = src.tags.get('shikigami_target')

136 if not tgt or tgt.dead: return arg

137

138 g = self.game

139 current = PlayerTurn.get_current(g).target

140 if current is not src: return arg

141

142 origin = src if 'shikigami_tag' in src.tags else tgt

143 if origin.tags['shikigami_tag'] != origin.tags['turn_count']:

144 return arg

145

146 dist2 = LaunchCard.calc_raw_distance(g, tgt, AttackCard())

147

148 for k in dist2:

149 if dist[k] > 0 and dist2[k] <= 1:

150 dist[k] = 0

151

152 return arg

153

154

155@register_character_to('common', '-kof')

156 class Chen(Character):

157 skills = [FlyingSkanda, Shikigami]

158 eventhandlers = [FlyingSkandaHandler, ShikigamiHandler]

159 maxlife = 4