# Coverage for thb/characters/kaguya.py : 23%

## 120 statements 39 run 81 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 from typing import Optional

6

7# -- third party --

8# -- own --

9 from thb.actions import Damage, DrawCards, LaunchCard, LifeLost, PlayerTurn, UserAction

10 from thb.actions import migrate_cards, skill_check, skill_wrap, user_choose_cards

11 from thb.cards.base import Card, Skill, VirtualCard, t_None

12 from thb.cards.classes import Heal, SealingArrayCard, TreatAs

13 from thb.cards.definition import BasicCard

14 from thb.characters.base import Character, register_character_to

15 from thb.inputlets import ChooseOptionInputlet

16 from thb.mode import THBEventHandler

17

18

19# -- code --

20 class Dilemma(Skill):

21 associated_action = None

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

23 target = t_None()

24

25

26 class DilemmaDamageAction(UserAction):

27 card_usage = 'handover'

28

29 def apply_action(self):

30 src = self.source

31 tgt = self.target

32

33 cards = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))

34 g = self.game

35 if cards:

36 self.peer_action = 'card'

37 g.players.exclude(tgt).reveal(cards)

38 migrate_cards(cards, src.cards)

39 else:

40 self.peer_action = 'life'

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

42

43 return True

44

45 def cond(self, cards):

46 if len(cards) != 1: return False

47 card = cards[0]

48 if card.is_card(Skill):

49 return False

50

51 if card.resides_in.type not in (

52 'cards', 'showncards', 'equips'

53 ): return False

54

55 return card.suit == Card.DIAMOND

56

57

58 class DilemmaHealAction(DrawCards):

59 def __init__(self, source, target, amount=2):

60 self.source = source

61 self.target = target

62 self.amount = amount

63 self.back = False

64

65

66 class DilemmaHandler(THBEventHandler):

67 interested = ['action_after']

68 execute_after = [

69 'DyingHandler',

70 'AyaRoundfanHandler',

71 'NenshaPhoneHandler',

72 ]

73

74 def handle(self, evt_type, act):

75 if evt_type != 'action_after': return act

76 if not isinstance(act, (Damage, Heal)): return act

77

78 src = act.source

79 tgt = act.target

80 if tgt.dead: return act

81 if not tgt.has_skill(Dilemma): return act

82 if not src: return act

83

84 self.dilemma_type = 'negative' if isinstance(act, Damage) else 'positive'

85 g = self.game

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

87 return act

88

89 if isinstance(act, Damage):

90 g.process_action(DilemmaDamageAction(tgt, src))

91 else: # Heal

92 g.process_action(DilemmaHealAction(tgt, src, 1))

93

94 return act

95

96

97 class ImperishableNight(TreatAs, Skill):

98 treat_as = SealingArrayCard

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

100

101 def check(self):

102 g = self.game

103 current = PlayerTurn.get_current(g).target

104 return current is not self.character

105

106

107 class ImperishableNightHandler(THBEventHandler):

108 interested = ['action_after']

109 card_usage = 'launch'

110

111 def handle(self, evt_type, act):

112 if evt_type != 'action_after': return act

113 if not isinstance(act, LaunchCard): return act

114

115 g = self.game

116

117 card = act.card

118 if not card: return act

119 if not isinstance(card, BasicCard): return act

120 if card.color != Card.RED: return act

121

122 if card.is_card(VirtualCard):

123 rawcards = VirtualCard.unwrap([card])

124 else:

125 rawcards = [card]

126

127 if not all(

128 c.resides_in is None or c.resides_in.type == 'droppedcard'

129 for c in rawcards

130 ): return act

131

132 tgt = act.source

133 self.target = tgt # for ui

134

135 if tgt.dead: return act

136

137 current: Optional[Character]

138

139 try:

140 current = PlayerTurn.get_current(g).target

141 except IndexError:

142 current = None

143

144 for p in g.players:

145 if p.dead or p is tgt: continue

146 if not p.has_skill(ImperishableNight): continue

147 if p is current: continue

148

149 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):

150 continue

151

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

153

154 if cards:

155 g.players.reveal(cards)

156 skill = skill_wrap(p, [ImperishableNight], cards, {})

157 assert skill_check(skill) # should not fail

158 g.deck.register_vcard(skill)

159 rst = g.process_action(LaunchCard(p, [tgt], skill))

160 assert rst

161

162 return act

163

164 def cond(self, cards):

165 if len(cards) != 1: return False

166 card = cards[0]

167 if card.resides_in.type not in (

168 'cards', 'showncards', 'equips'

169 ): return False

170 if 'skill' in card.category: return False

171 if card.color != Card.RED: return False

172 return bool(set(card.category) & {'basic', 'equipment'})

173

174 def ask_for_action_verify(self, p, cl, tl):

175 tgt = self.target

176 skill = skill_wrap(p, [ImperishableNight], cl, {})

177 return LaunchCard(p, [tgt], skill).can_fire()

178

179

180@register_character_to('common')

181 class Kaguya(Character):

182 skills = [Dilemma, ImperishableNight]

183 eventhandlers = [DilemmaHandler, ImperishableNightHandler]

184 maxlife = 3