Coverage for thb/characters/kaguya.py : 81%
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 Damage, DrawCards, LaunchCard, LifeLost, PlayerTurn, UserAction
8from thb.actions import migrate_cards, skill_check, skill_wrap, user_choose_cards
9from thb.cards.base import Card, Skill, VirtualCard, t_None
10from thb.cards.classes import Heal, SealingArrayCard, TreatAs
11from thb.cards.definition import BasicCard
12from thb.characters.base import Character, register_character_to
13from thb.inputlets import ChooseOptionInputlet
14from thb.mode import THBEventHandler
17# -- code --
18class Dilemma(Skill):
19 associated_action = None
20 skill_category = ['character', 'passive']
21 target = t_None()
24class DilemmaDamageAction(UserAction):
25 card_usage = 'handover'
27 def apply_action(self):
28 src = self.source
29 tgt = self.target
31 cards = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))
32 g = self.game
33 if cards: 33 ↛ 34line 33 didn't jump to line 34, because the condition on line 33 was never true
34 self.peer_action = 'card'
35 g.players.exclude(tgt).reveal(cards)
36 migrate_cards(cards, src.cards)
37 else:
38 self.peer_action = 'life'
39 g.process_action(LifeLost(src, tgt, 1))
41 return True
43 def cond(self, cards):
44 if len(cards) != 1: return False
45 card = cards[0]
46 if card.is_card(Skill): 46 ↛ 49line 46 didn't jump to line 49, because the condition on line 46 was never false
47 return False
49 if card.resides_in.type not in (
50 'cards', 'showncards', 'equips'
51 ): return False
53 return card.suit == Card.DIAMOND
56class DilemmaHealAction(DrawCards):
57 def __init__(self, source, target, amount=2):
58 self.source = source
59 self.target = target
60 self.amount = amount
61 self.back = False
64class DilemmaHandler(THBEventHandler):
65 interested = ('action_after',)
66 execute_after = (
67 'DyingHandler',
68 'AyaRoundfanHandler',
69 'NenshaPhoneHandler',
70 )
72 def handle(self, evt_type, act):
73 if evt_type != 'action_after': return act 73 ↛ exitline 73 didn't return from function 'handle', because the return on line 73 wasn't executed
74 if not isinstance(act, (Damage, Heal)): return act
76 src = act.source
77 tgt = act.target
78 if tgt.dead: return act
79 if not tgt.has_skill(Dilemma): return act
80 if not src: return act
82 self.dilemma_type = 'negative' if isinstance(act, Damage) else 'positive'
83 g = self.game
84 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
85 return act
87 if isinstance(act, Damage):
88 g.process_action(DilemmaDamageAction(tgt, src))
89 else: # Heal
90 g.process_action(DilemmaHealAction(tgt, src, 1))
92 return act
95class ImperishableNight(TreatAs, Skill):
96 treat_as = SealingArrayCard
97 skill_category = ['character', 'passive']
99 def check(self):
100 g = self.game
101 current = PlayerTurn.get_current(g).target
102 return current is not self.character
105class ImperishableNightHandler(THBEventHandler):
106 interested = ['action_after']
107 card_usage = 'launch'
109 def handle(self, evt_type, act):
110 if evt_type != 'action_after': return act 110 ↛ exitline 110 didn't return from function 'handle', because the return on line 110 wasn't executed
111 if not isinstance(act, LaunchCard): return act
113 g = self.game
115 card = act.card
116 if not card: return act 116 ↛ exitline 116 didn't return from function 'handle', because the return on line 116 wasn't executed
117 if not isinstance(card, BasicCard): return act
118 if card.color != Card.RED: return act
120 if card.is_card(VirtualCard): 120 ↛ 121line 120 didn't jump to line 121, because the condition on line 120 was never true
121 rawcards = VirtualCard.unwrap([card])
122 else:
123 rawcards = [card]
125 if not all( 125 ↛ exitline 125 didn't jump to the function exit
126 c.resides_in is None or c.resides_in.type == 'droppedcard'
127 for c in rawcards
128 ): return act
130 tgt = act.source
131 self.target = tgt # for ui
133 if tgt.dead: return act 133 ↛ exitline 133 didn't return from function 'handle', because the return on line 133 wasn't executed
135 try:
136 current = PlayerTurn.get_current(g).target
137 except IndexError:
138 current = None
140 for p in g.players:
141 if p.dead or p is tgt: continue
142 if not p.has_skill(ImperishableNight): continue
143 if p is current: continue 143 ↛ 140line 143 didn't jump to line 140, because the continue on line 143 wasn't executed
145 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):
146 continue
148 cards = user_choose_cards(self, p, ('cards', 'showncards', 'equips'))
150 if cards: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true
151 g.players.reveal(cards)
152 skill = skill_wrap(p, [ImperishableNight], cards, {})
153 assert skill_check(skill) # should not fail
154 g.deck.register_vcard(skill)
155 rst = g.process_action(LaunchCard(p, [tgt], skill))
156 assert rst
158 return act
160 def cond(self, cards):
161 if len(cards) != 1: return False
162 card = cards[0]
163 if card.resides_in.type not in ( 163 ↛ exitline 163 didn't jump to the function exit
164 'cards', 'showncards', 'equips'
165 ): return False
166 if 'skill' in card.category: return False 166 ↛ 167line 166 didn't jump to line 167, because the condition on line 166 was never false
167 if card.color != Card.RED: return False
168 return bool(set(card.category) & {'basic', 'equipment'})
170 def ask_for_action_verify(self, p, cl, tl):
171 tgt = self.target
172 skill = skill_wrap(p, [ImperishableNight], cl, {})
173 return LaunchCard(p, [tgt], skill).can_fire()
176@register_character_to('common')
177class Kaguya(Character):
178 skills = [Dilemma, ImperishableNight]
179 eventhandlers = [DilemmaHandler, ImperishableNightHandler]
180 maxlife = 3