# Coverage for thb/characters/yuyuko.py : 28%

## 134 statements 50 run 84 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 DrawCards, DropCardStage, DummyAction, FinalizeStage, GenericAction

8 from thb.actions import LifeLost, MaxLifeChange, Pindian, PlayerDeath, PlayerTurn, TryRevive

9 from thb.actions import UserAction, ttags

10 from thb.cards.base import Skill, t_None, t_OtherOne

11 from thb.cards.basic import Heal

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 GuidedDeathLifeLost(LifeLost):

19 pass

20

21

22 class GuidedDeathEffect(GenericAction):

23 def __init__(self, source, target_list):

24 self.source = source

25 self.target = source

26 self.target_list = target_list

27

28 def apply_action(self):

29 g = self.game

30

31 for p in self.target_list:

32 g.process_action(GuidedDeathLifeLost(p, p))

33

34 return True

35

36

37 class GuidedDeathAction(UserAction):

38 def apply_action(self):

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

40 if tgt.dead: return True

41 g = self.game

42 ttags(src)['guided_death_active_use'] = tgt

43 g.process_action(GuidedDeathLifeLost(src, tgt, 1))

44 return True

45

46

47 class GuidedDeath(Skill):

48 associated_action = GuidedDeathAction

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

50 target = t_OtherOne()

51 usage = 'drop'

52

53 def check(self):

54 cl = self.associated_cards

55 return len(cl) == 0

56

57

58 class GuidedDeathHandler(THBEventHandler):

59 interested = ['action_apply']

60 execute_before = ['SoulDrainHandler']

61

62 def handle(self, evt_type, act):

63 if evt_type == 'action_apply' and isinstance(act, FinalizeStage):

64 g = self.game

65

66 src = act.target

67 if not src.has_skill(GuidedDeath) or src.dead:

68 return act

69

70 p = ttags(src)['guided_death_active_use']

71 if p:

72 g.process_action(Heal(p, p, 1))

73 else:

74 tl = [p for p in g.players.rotate_to(src) if p.life == 1 and p is not src]

75 if not tl:

76 return act

77

78 g.process_action(GuidedDeathEffect(src, tl))

79

80 return act

81

82

83 class SoulDrain(Skill):

84 associated_action = None

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

86 target = t_None()

87

88

89 class SoulDrainEffect(GenericAction):

90 def apply_action(self):

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

92 g = self.game

93

94 assert tgt.life <= 0

95

96 g.process_action(DrawCards(src, 1))

97

98 if not tgt.cards and not tgt.showncards:

99 return True

100

101 if src is tgt:

102 return True

103

104 if g.user_input([src], ChooseOptionInputlet(self, (False, True))):

105 if g.process_action(Pindian(src, tgt)):

106 g.process_action(MaxLifeChange(src, tgt, -tgt.maxlife + 1))

107 else:

108 g.process_action(Heal(src, tgt, -tgt.life + 1))

109

110 return True

111

112

113 class SoulDrainHandler(THBEventHandler):

114 interested = ['action_before']

115

116 def handle(self, evt_type, act):

117 if evt_type == 'action_before' and isinstance(act, TryRevive):

118 g = self.game

119 tgt = act.target

120

121 for p in g.players:

122 if p.has_skill(SoulDrain) and not p.dead and p is not tgt:

123 break

124 else:

125 return act

126

127 for a in reversed(g.action_stack):

128 if isinstance(a, PlayerTurn):

129 if a.target is not p:

130 return act

131 else:

132 break

133 else:

134 return act

135

136 g.process_action(SoulDrainEffect(p, tgt))

137

138 if tgt.life > 0:

139 # Abort TryRevive process

140 return DummyAction(p, act.target)

141 else:

142 return act

143

144 return act

145

146

147 class PerfectCherryBlossomHandler(THBEventHandler):

148 interested = ['action_apply', 'action_before']

149 execute_after = ['DeathHandler']

150

151 def handle(self, evt_type, act):

152 if evt_type == 'action_apply' and isinstance(act, PlayerDeath):

153 g = self.game

154

155 for p in g.players:

156 if p.has_skill(PerfectCherryBlossom) and not p.dead and p is not act.target:

157 break

158 else:

159 return act

160

161 g.process_action(PerfectCherryBlossomExtractAction(p, act.target))

162

163 elif evt_type == 'action_before' and isinstance(act, DropCardStage):

164 g = self.game

165 tgt = act.target

166 if not tgt.has_skill(PerfectCherryBlossom):

167 return act

168

169 act.dropn -= tgt.maxlife - tgt.life

170

171 return act

172

173

174 class PerfectCherryBlossomExtractAction(UserAction):

175 def apply_action(self):

176 g = self.game

177 src = self.source

178 if src.life >= src.maxlife:

179 choice = 'maxlife'

180 elif g.user_input([src], ChooseOptionInputlet(self, ('life', 'maxlife'))) == 'maxlife':

181 choice = 'maxlife'

182 else:

183 choice = 'life'

184

185 if choice == 'life':

186 g.process_action(Heal(src, src, 1))

187 else:

188 g.process_action(MaxLifeChange(src, src, 1))

189

190 return True

191

192

193 class PerfectCherryBlossom(Skill):

194 associated_action = None

195 skill_category = ('character', 'passive')

196 target = t_None()

197

198

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

200 class Yuyuko(Character):

201 skills = [GuidedDeath, SoulDrain, PerfectCherryBlossom]

202 eventhandlers = [GuidedDeathHandler, SoulDrainHandler, PerfectCherryBlossomHandler]

203 maxlife = 3