Coverage for thb/characters/ran.py : 70%
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
4# -- stdlib --
5# -- third party --
6# -- own --
7from thb.actions import ActionLimitExceeded, ActionStageLaunchCard, Damage, DropCards, GenericAction
8from thb.actions import PlayerTurn, PrepareStage, ttags, user_choose_cards
9from thb.cards.base import DummyCard, Skill, VirtualCard, t_None
10from thb.cards.classes import InstantSpellCardAction, Reject, SpellCardAction, TreatAs
11from thb.characters.base import Character, register_character_to
12from thb.inputlets import ChooseOptionInputlet, ProphetInputlet
13from thb.mode import THBEventHandler
16# -- code --
17class Prophet(Skill):
18 associated_action = None
19 skill_category = ['character', 'passive']
20 target = t_None()
23class ExtremeIntelligence(Skill):
24 associated_action = None
25 skill_category = ['character', 'passive']
26 target = t_None()
29class ProphetAction(GenericAction):
30 def apply_action(self):
31 tgt = self.target
32 g = self.game
33 n = min(len([p for p in g.players if not p.dead]), 5)
34 cards = g.deck.getcards(n)
36 assert cards == g.deck.getcards(n)
38 tgt.reveal(cards)
40 upcards, downcards = g.user_input([tgt], ProphetInputlet(self, cards), timeout=45) or [list(range(n)), []]
42 deck = g.deck.cards
43 for i, c in enumerate(downcards): 43 ↛ 44line 43 didn't jump to line 44, because the loop on line 43 never started
44 deck[i] = c
45 deck.rotate(-len(downcards))
47 for i, c in enumerate(upcards):
48 deck[i] = c
50 assert g.deck.getcards(len(upcards)) == upcards
52 return True
55class ProphetHandler(THBEventHandler):
56 interested = ['action_apply']
58 def handle(self, evt_type, act):
59 if evt_type == 'action_apply' and isinstance(act, PrepareStage):
60 tgt = act.target
61 if not tgt.has_skill(Prophet): return act
62 g = self.game
63 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
64 return act
66 g.process_action(ProphetAction(tgt, tgt))
68 return act
71class ExtremeIntelligenceAction(GenericAction):
72 card_usage = 'drop'
74 def __init__(self, source, target, act):
75 self.source, self.target, self.action = \
76 source, act.target, act
78 def activate_action(self):
79 p = self.source
80 self.cards = cards = user_choose_cards(self, p, ('cards', 'showncards', 'equips'))
81 if not cards: return False 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never false
82 p.tags['ran_ei'] = p.tags['turn_count'] + 1
83 return True
85 def apply_action(self):
86 p = self.source
87 cards = self.cards
88 g = self.game
89 g.process_action(DropCards(p, p, cards))
91 act = self.action
92 nact = act.__class__(source=p, target=act.target)
93 try:
94 nact.target_list = act.target_list
95 except AttributeError:
96 pass
98 try:
99 # HACK: This is for actions triggered by ForEach action.
100 # Well, actually it's for Harvest since only this
101 # uses the attrib
102 nact.parent_action = act.parent_action
103 except AttributeError:
104 pass
106 try:
107 nact.associated_card = act.associated_card
108 except AttributeError:
109 pass
111 g.process_action(nact)
112 return True
114 def cond(self, cl):
115 return len(cl) == 1 and not cl[0].is_card(Skill)
118class ExtremeIntelligenceHandler(THBEventHandler):
119 interested = ['action_after', 'game_begin']
121 def handle(self, evt_type, act):
122 if evt_type == 'action_after' and isinstance(act, InstantSpellCardAction):
123 if isinstance(act, Reject): return act
124 g = self.game
126 tgt = PlayerTurn.get_current(g).target
127 for p in g.players.exclude(tgt):
128 if p.dead:
129 continue
131 if not p.has_skill(ExtremeIntelligence):
132 continue
134 if p.tags['ran_ei'] >= p.tags['turn_count'] + 1: 134 ↛ 135line 134 didn't jump to line 135, because the condition on line 134 was never true
135 continue
137 try:
138 tl = act.target_list
139 except AttributeError:
140 tl = [act.target]
142 if any(t.dead for t in tl):
143 return act
145 if not act.can_fire():
146 return act # act cannot fire again
148 nact = ExtremeIntelligenceAction(p, act.target, act)
150 if nact.activate_action(): 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true
151 g.process_action(nact)
153 elif evt_type == 'game_begin':
154 g = self.game
155 for p in g.players:
156 if isinstance(p, Ran):
157 p.tags['ran_ei'] = 0 # for ui
159 return act
162class ExtremeIntelligenceKOF(TreatAs, Skill):
163 skill_category = ['character', 'active']
165 def __init__(self, player):
166 Skill.__init__(self, player)
167 self.treat_as = ttags(player).get('ran_eikof_card') or DummyCard
169 def check(self):
170 cards = self.associated_cards
171 if len(cards) != 1: return False
172 c = cards[0]
173 if c.resides_in is None: return False
174 if c.resides_in.type not in ('cards', 'showncards'): return False
175 return True
178class ExtremeIntelligenceKOFHandler(THBEventHandler):
179 interested = ['action_apply', 'action_shootdown']
181 def handle(self, evt_type, act):
182 if evt_type == 'action_apply' and isinstance(act, ActionStageLaunchCard):
183 if not act.card.is_card(VirtualCard):
184 src = act.source
185 if not src.has_skill(ExtremeIntelligenceKOF): return act
186 c = act.card
187 if 'instant_spellcard' not in c.category: return act
188 ttags(src)['ran_eikof_card'] = c.__class__
190 elif act.card.is_card(ExtremeIntelligenceKOF):
191 src = act.source
192 ttags(src)['ran_eikof_tag'] = True
194 elif evt_type == 'action_shootdown' and isinstance(act, ActionStageLaunchCard) and act.card.is_card(ExtremeIntelligenceKOF):
195 src = act.source
196 tt = ttags(src)
197 if tt['ran_eikof_tag'] or not tt['ran_eikof_card']:
198 raise ActionLimitExceeded
200 return act
203class NakedFox(Skill):
204 associated_action = None
205 skill_category = ['character', 'passive', 'compulsory']
206 target = t_None()
209class NakedFoxAction(GenericAction):
210 def __init__(self, dmg):
211 self.source = self.target = dmg.target
212 self.dmgact = dmg
213 self.dmgamount = dmg.amount # for UI
215 def apply_action(self):
216 self.dmgact.amount -= 1
217 return True
220class NakedFoxHandler(THBEventHandler):
221 interested = ['action_before']
223 def handle(self, evt_type, act):
224 if evt_type == 'action_before' and isinstance(act, Damage):
225 g = self.game
226 tgt = act.target
227 if not tgt.has_skill(NakedFox): return act
228 pact = g.action_stack[-1]
229 if not isinstance(pact, SpellCardAction): return act
230 if tgt.cards or tgt.showncards: return act
231 if act.amount < 1: return act 231 ↛ exitline 231 didn't return from function 'handle', because the return on line 231 wasn't executed
233 g.process_action(NakedFoxAction(act))
234 return act
236 return act
239@register_character_to('common', '-kof')
240class Ran(Character):
241 skills = [Prophet, ExtremeIntelligence, NakedFox]
242 eventhandlers = [
243 ProphetHandler,
244 ExtremeIntelligenceHandler,
245 NakedFoxHandler,
246 ]
247 maxlife = 3
250@register_character_to('kof')
251class RanKOF(Character):
252 skills = [Prophet, ExtremeIntelligenceKOF, NakedFox]
253 eventhandlers = [
254 ProphetHandler,
255 ExtremeIntelligenceKOFHandler,
256 NakedFoxHandler,
257 ]
258 maxlife = 3