# Coverage for thb/characters/reisen.py : 36%

## 105 statements 50 run 55 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 ActionStageLaunchCard, Damage, FinalizeStage, LaunchCard, PlayerTurn

9 from thb.actions import UserAction

10 from thb.cards.base import Skill

11 from thb.cards.classes import AttackCard, DuelCard, Heal, HealCard, PhysicalCard, 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 Lunatic(Skill):

19 associated_action = None

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

21 target = t_None()

22

23

24 class Discarder(Skill):

25 associated_action = None

26 distance = 1

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

28 target = t_None()

29

30

31 class MahjongDrug(Skill):

32 associated_action = None

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

34 target = t_None()

35

36

37 class LunaticAction(UserAction):

38 def apply_action(self):

39 self.target.skills.append(Discarder)

40 self.target.tags['reisen_discarder'] = True # for tag

41 return True

42

43

44 class LunaticHandler(THBEventHandler):

45 interested = ['action_after']

46

47 def handle(self, evt_type, act):

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

49 src = act.source

50 if not src: return act

51 if not src.has_skill(Lunatic): return act

52

53 tgt = act.target

54 if tgt.dead or tgt.has_skill(Discarder): return act

55

56 g = self.game

57 for lc in reversed(g.action_stack):

58 if isinstance(lc, LaunchCard):

59 break

60 else:

61 return act

62

63 c = lc.card

64 if not (c.is_card(AttackCard) or c.is_card(DuelCard)):

65 return act

66

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

68 g.process_action(LunaticAction(src, tgt))

69

70 return act

71

72

73 class DiscarderAttackOnly(ActionShootdown):

74 pass

75

76

77 class DiscarderDistanceLimit(ActionShootdown):

78 pass

79

80

81 class DiscarderHandler(THBEventHandler):

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

83

84 def handle(self, evt_type, act):

85 if evt_type == 'action_shootdown' and isinstance(act, ActionStageLaunchCard):

86 src = act.source

87 if not src.has_skill(Discarder): return act

88 g = self.game

89 current = PlayerTurn.get_current(g).target

90 if src is not current: return act

91

92 self.card = c = act.card

93 if not c.is_card(AttackCard):

94 if c.is_card(PhysicalCard) or 'treat_as' in c.category:

95 raise DiscarderAttackOnly

96 else:

97 return act

98

99 dist = LaunchCard.calc_distance(g, src, Discarder(src))

100 dist.pop(src, '')

101 nearest = max(min(dist.values()), 0)

102 avail = {p for p in dist if dist[p] <= nearest}

103

104 if not set(act.target_list) <= avail:

105 raise DiscarderDistanceLimit

106

107 elif evt_type == 'action_after' and isinstance(act, FinalizeStage):

108 tgt = act.target

109 if tgt.has_skill(Discarder):

110 tgt.skills.remove(Discarder)

111 tgt.tags['reisen_discarder'] = False # for tag

112

113 return act

114

115

116 class MahjongDrugAction(UserAction):

117 def apply_action(self):

118 self.target.tags['wine'] = True

119 return True

120

121 def is_valid(self):

122 return not self.target.dead

123

124

125 class MahjongDrugHandler(THBEventHandler):

126 interested = ['action_after']

127

128 def handle(self, evt_type, act):

129 if evt_type == 'action_after' and isinstance(act, Heal):

130 tgt = act.target

131 if not tgt.has_skill(MahjongDrug): return act

132 card = getattr(act, 'associated_card', None)

133 if not card or not card.is_card(HealCard): return act

134

135 g = self.game

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

137 g.process_action(MahjongDrugAction(tgt, tgt))

138

139 return act

140

141

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

143 class Reisen(Character):

144 skills = [Lunatic, MahjongDrug]

145 eventhandlers = [DiscarderHandler, LunaticHandler, MahjongDrugHandler]

146 maxlife = 4

147

148

149@register_character_to('kof')

150 class ReisenKOF(Character):

151 skills = [Lunatic]

152 eventhandlers = [DiscarderHandler, LunaticHandler]

153 maxlife = 4