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 TYPE_CHECKING 

6 

7# -- third party -- 

8# -- own -- 

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

10from thb.actions import user_choose_players 

11from thb.cards.base import Skill 

12from thb.cards.classes import AttackCard, Duel, InstantSpellCardAction, Reject, TreatAs, t_None 

13from thb.characters.base import Character, register_character_to 

14from thb.inputlets import ChooseOptionInputlet 

15from thb.mode import THBEventHandler 

16 

17# -- typing -- 

18if TYPE_CHECKING: 

19 from thb.thbkof import THBattleKOF # noqa: F401 

20 

21 

22# -- code -- 

23class ReversedScales(TreatAs, Skill): 

24 skill_category = ['character', 'active', 'compulsory'] 

25 treat_as = AttackCard 

26 

27 def check(self): 

28 cl = self.associated_cards 

29 if not cl or len(cl) != 1: 

30 return False 

31 

32 g = self.game 

33 if PlayerTurn.get_current(g).target is self.character: 

34 return False 

35 

36 return cl[0].resides_in.type in ('cards', 'showncards') 

37 

38 

39class Sadist(Skill): 

40 associated_action = None 

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

42 target = t_None() 

43 

44 

45class SadistKOF(Skill): 

46 associated_action = None 

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

48 target = t_None() 

49 

50 

51class SadistKOFDrawCards(DrawCards): 

52 pass 

53 

54 

55class SadistKOFDamageAction(UserAction): 

56 def apply_action(self): 

57 g = self.game 

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

59 return g.process_action(Damage(src, tgt, 1)) 

60 

61 

62class SadistKOFHandler(THBEventHandler): 

63 interested = ['action_after', 'character_debut'] 

64 execute_after = ['DeathHandler'] 

65 game: THBattleKOF 

66 

67 def handle(self, evt_type, arg): 

68 if evt_type == 'action_after' and isinstance(arg, PlayerDeath): 

69 tgt = arg.target 

70 g = self.game 

71 op = g.get_opponent(tgt) 

72 if arg.source is op and op.has_skill(SadistKOF): 

73 g.process_action(SadistKOFDrawCards(op, 2)) 

74 op.tags['sadist_kof_fire'] = True 

75 

76 elif evt_type == 'character_debut': 

77 old, new = arg 

78 if not old: return arg 

79 

80 g = self.game 

81 op = g.get_opponent(new) 

82 

83 if op.has_skill(SadistKOF) and op.tags['sadist_kof_fire']: 

84 op.tags['sadist_kof_fire'] = False 

85 g.process_action(SadistKOFDamageAction(op, new)) 

86 

87 return arg 

88 

89 

90class ReversedScalesAction(UserAction): 

91 def __init__(self, target, action): 

92 self.source = target 

93 self.target = target 

94 self.action = action 

95 

96 def apply_action(self): 

97 self.action.__class__ = Duel 

98 return True 

99 

100 

101class ReversedScalesHandler(THBEventHandler): 

102 interested = ['action_before'] 

103 execute_before = ['MaidenCostumeHandler'] 

104 

105 def handle(self, evt_type, act): 

106 if evt_type != 'action_before': 

107 return act 

108 

109 if not isinstance(act, InstantSpellCardAction): 

110 return act 

111 

112 if isinstance(act, Reject): 

113 # HACK 

114 return act 

115 

116 if ForEach.is_group_effect(act): 

117 return act 

118 

119 src = act.source 

120 tgt = act.target 

121 

122 if not src or tgt is src: 

123 return act 

124 

125 if not tgt.has_skill(ReversedScales): 

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(ReversedScalesAction(tgt, act)) 

133 

134 return act 

135 

136 

137class SadistAction(UserAction): 

138 def __init__(self, source, target): 

139 self.source = source 

140 self.target = target 

141 

142 def apply_action(self): 

143 g = self.game 

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

145 tgt.tags['sadist_target'] = False 

146 g.process_action(Damage(src, tgt, 1)) 

147 return True 

148 

149 

150class SadistHandler(THBEventHandler): 

151 interested = ['action_after', 'action_before'] 

152 card_usage = 'drop' 

153 execute_before = ['WineHandler'] 

154 execute_after = ['DeathHandler'] 

155 

156 def handle(self, evt_type, act): 

157 if evt_type == 'action_after' and isinstance(act, PlayerDeath): 

158 src = act.source 

159 if not src or not src.has_skill(Sadist): 

160 return act 

161 

162 g = self.game 

163 dist = LaunchCard.calc_distance(g, src, AttackCard()) 

164 candidates = [k for k, v in dist.items() if v <= 0 and k is not src] 

165 

166 if not candidates: 

167 return act 

168 

169 pl = user_choose_players(self, src, candidates) 

170 if pl: 

171 self.game.process_action(SadistAction(src, pl[0])) 

172 

173 elif evt_type == 'action_before' and isinstance(act, Damage): 

174 src = act.source 

175 tgt = act.target 

176 

177 if not src or src is tgt: 

178 return act 

179 

180 if not src.has_skill(Sadist): 

181 return act 

182 

183 if tgt.life == 1: 

184 act.amount += 1 

185 

186 return act 

187 

188 def choose_player_target(self, pl): 

189 return pl[-1:], len(pl) 

190 

191 

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

193class Yuuka(Character): 

194 skills = [ReversedScales, Sadist] 

195 eventhandlers = [ReversedScalesHandler, SadistHandler] 

196 maxlife = 4 

197 

198 

199@register_character_to('kof') 

200class YuukaKOF(Character): 

201 skills = [ReversedScales, SadistKOF] 

202 eventhandlers = [ReversedScalesHandler, SadistKOFHandler] 

203 maxlife = 4