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

## 109 statements 42 run 67 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 game.base import ActionShootdown

8 from thb.actions import Damage, DrawCards, DropCards, FatetellStage, GenericAction, LaunchCard

9 from thb.actions import LifeLost, ShowCards, UseCard, UserAction, user_choose_cards

10 from thb.cards.base import Card, Skill, VirtualCard

11 from thb.cards.classes import Wine, t_None

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class Ciguatera(Skill):

19 associated_action = None

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

21 target = t_None()

22

23

24 class CiguateraAction(UserAction):

25 def __init__(self, source, target, cards):

26 self.source = source

27 self.target = target

28 self.cards = cards

29

30 def apply_action(self):

31 tgt = self.target

32 src = self.source

33 g = self.game

34 g.process_action(DropCards(src, src, self.cards))

35 g.process_action(LifeLost(src, tgt, 1))

36 g.process_action(Wine(src, tgt))

37

38 return True

39

40

41 class CiguateraHandler(THBEventHandler):

42 interested = ['action_before']

43 card_usage = 'drop'

44

45 def handle(self, evt_type, act):

46 if evt_type == 'action_before' and isinstance(act, FatetellStage):

47 if act.target.dead: return act

48 g = self.game

49 for p in g.players:

50 if p.dead:

51 continue

52

53 if not p.has_skill(Ciguatera):

54 continue

55

56 cards = user_choose_cards(self, p, ('cards', 'showncards', 'equips'))

57 if cards:

58 g.process_action(CiguateraAction(p, act.target, cards))

59

60 return act

61

62 def cond(self, cl):

63 return \

64 len(cl) == 1 and \

65 not cl[0].is_card(Skill) and \

66 cl[0].resides_in.type in ('cards', 'showncards', 'equips') and \

67 cl[0].color == Card.BLACK

68

69

70 class Melancholy(Skill):

71 associated_action = None

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

73 target = t_None()

74

75

76 class MelancholyPoison(VirtualCard):

77 associated_action = None

78 target = t_None()

79

80

81 class MelancholyLimit(ActionShootdown):

82 pass

83

84

85 class MelancholyAction(GenericAction):

86 def __init__(self, source, target, amount):

87 self.source = source

88 self.target = target

89 self.amount = amount

90

91 def apply_action(self):

92 src = self.source

93 tgt = self.target

94 draw = DrawCards(src, self.amount)

95 g = self.game

96 g.process_action(draw)

97 g.process_action(ShowCards(src, draw.cards))

98 if [c for c in draw.cards if c.suit != Card.CLUB]: # any non-club

99 tgt.tags['melancholy_tag'] = g.turn_count

100 self.effective = True

101

102 else:

103 self.effective = False

104

105 return True

106

107

108 class MelancholyHandler(THBEventHandler):

109 interested = ['action_after', 'action_shootdown']

110 execute_after = [

111 'DyingHandler',

112 'AyaRoundfanHandler',

113 'NenshaPhoneHandler',

114 ]

115

116 def handle(self, evt_type, act):

117 if evt_type == 'action_after' and isinstance(act, Damage):

118 act = act

119 tgt = act.target

120 src = act.source

121

122 if not src:

123 return act

124

125 if not tgt.has_skill(Melancholy):

126 return act

127

128 g = self.game

129 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):

130 return act

131

132 g.process_action(MelancholyAction(tgt, src, amount=1))

133

134 elif evt_type == 'action_shootdown' and isinstance(act, (LaunchCard, UseCard)):

135 src = act.source

136 g = self.game

137 if src.tags.get('melancholy_tag') != g.turn_count:

138 return act

139

140 def walk(c):

141 if not c.is_card(VirtualCard):

142 return [c]

143

144 if c.usage not in ('launch', 'use'):

145 return []

146

147 cards = c.associated_cards

148 return walk(cards[0]) if len(cards) == 1 else cards

149

150 cards = walk(act.card)

151 zone = src.cards, src.showncards

152 for c in cards:

153 if c.resides_in in zone:

154 raise MelancholyLimit

155

156 return act

157

158

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

160 class Medicine(Character):

161 skills = [Ciguatera, Melancholy]

162 eventhandlers = [CiguateraHandler, MelancholyHandler]

163 maxlife = 3