Hide keyboard shortcuts

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 -*- 

2from __future__ import annotations 

3 

4# -- stdlib -- 

5from typing import Optional 

6 

7# -- third party -- 

8# -- own -- 

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

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

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

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

13from thb.cards.definition import BasicCard 

14from thb.characters.base import Character, register_character_to 

15from thb.inputlets import ChooseOptionInputlet 

16from thb.mode import THBEventHandler 

17 

18 

19# -- code -- 

20class Dilemma(Skill): 

21 associated_action = None 

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

23 target = t_None() 

24 

25 

26class 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 

58class 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 

66class 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 

97class 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 

107class 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') 

181class Kaguya(Character): 

182 skills = [Dilemma, ImperishableNight] 

183 eventhandlers = [DilemmaHandler, ImperishableNightHandler] 

184 maxlife = 3