# Coverage for thb/characters/eirin.py : 30%

## 113 statements 42 run 71 missing 0 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# -- third party --

6# -- own --

7 from thb.actions import DropCards, GenericAction, LaunchCard, ShowCards, UserAction, migrate_cards

8 from thb.actions import random_choose_card, ttags

9 from thb.cards.base import Card, HiddenCard, PhysicalCard, Skill, TreatAs, VirtualCard, t_One

10 from thb.cards.basic import Heal

11 from thb.cards.definition import AttackCard

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class EirinHeal(Heal):

19 pass

20

21

22 class SkySilkAction(UserAction):

23 def apply_action(self):

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

25 g = self.game

26

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

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

29 g.players.reveal(c)

30

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

32 if tgt.dead: return False

33

34 action = 'draw'

35 if tgt.life < tgt.maxlife:

36 action = g.user_input([tgt], ChooseOptionInputlet(self, ('heal', 'draw'))) or 'draw'

37

38 if action == 'heal':

39 g.process_action(EirinHeal(src, tgt))

40 else:

41 g.deck.getcards(3)

42 g.deck.cards.rotate(3)

43 cl = g.deck.getcards(3)

44 g.process_action(ShowCards(tgt, cl))

45

46 drop = [c for c in cl if 'basic' in c.category]

47 get = [c for c in cl if c not in drop]

48

49 if get:

50 migrate_cards(get, tgt.cards)

51

52 if drop:

53 migrate_cards(drop, g.deck.droppedcards)

54

55 ttags(src)['sky_silk'] = True

56 return True

57

58 def is_valid(self):

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

60 return (tgt.cards or tgt.showncards or tgt.equips) and not ttags(src)['sky_silk']

61

62

63 class SkySilk(Skill):

64 associated_action = SkySilkAction

65 skill_category = ('character', 'active')

66 target = t_One()

67 usage = 'drop'

68

69 def check(self):

70 cl = self.associated_cards

71 return bool(not cl)

72

73

74 class LunaStringPlaceCard(GenericAction):

75 def __init__(self, target, card):

76 self.source = self.target = target

77 self.card = card

78

79 def apply_action(self):

80 g = self.game

81 tgt = self.target

82 direction = g.user_input([tgt], ChooseOptionInputlet(self, ('front', 'back')))

83 sk = self.card

84 assert sk.is_card(LunaString)

85 c = sk.associated_cards[0]

86 sk.associated_cards[:] = []

87 sk.cost_detached = True

88 self.direction = direction # for ui

89 migrate_cards([c], g.deck.cards, unwrap=True, direction=direction)

90 return True

91

92

93 class LunaStringLaunchCardHandler(THBEventHandler):

94 interested = ['action_before']

95 execute_before = ['SolidShieldHandler']

96

97 def handle(self, evt_type, act):

98 if evt_type == 'action_before':

99 if not isinstance(act, LaunchCard):

100 return act

101

102 c = act.card

103

104 def walk(c):

105 if c.is_card(LunaString):

106 return c

107

108 if c.is_card(PhysicalCard) or c.is_card(HiddenCard):

109 return None

110

111 assert isinstance(c, VirtualCard)

112

113 for c1 in c.associated_cards:

114 r = walk(c1)

115 if r:

116 return r

117

118 return None

119

120 c = walk(c)

121

122 if c:

123 g = self.game

124 assert isinstance(c, LunaString)

125 g.process_action(LunaStringPlaceCard(c.character, c))

126

127 return act

128

129

130 class LunaString(TreatAs, Skill):

131 treat_as = AttackCard

132

133 skill_category = ('character', 'active')

134 no_reveal = True

135 use_action = LunaStringPlaceCard # UseCard hook

136

137 color = Card.NOTSET # type: ignore

138 suit = Card.NOTSET # type: ignore

139 number = 0 # type: ignore

140

141 cost_detached = False

142

143 def check(self):

144 if self.cost_detached:

145 return True

146

147 cl = self.associated_cards

148 if not len(cl) == 1:

149 return False

150

151 c = cl[0]

152

153 if not (c.is_card(PhysicalCard) or c.is_card(HiddenCard)): # HiddenCard for other viewpoints

154 return False

155

156 return c.resides_in.type in ('cards', 'showncards')

157

158

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

160 class Eirin(Character):

161 skills = [SkySilk, LunaString]

162 eventhandlers = [LunaStringLaunchCardHandler]

163 maxlife = 3