Coverage for thb/characters/kaguya.py : 23%
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 --
5from typing import Optional
7# -- third party --
8# -- own --
9from thb.actions import Damage, DrawCards, LaunchCard, LifeLost, PlayerTurn, UserAction
10from thb.actions import migrate_cards, skill_check, skill_wrap, user_choose_cards
11from thb.cards.base import Card, Skill, VirtualCard, t_None
12from thb.cards.classes import Heal, SealingArrayCard, TreatAs
13from thb.cards.definition import BasicCard
14from thb.characters.base import Character, register_character_to
15from thb.inputlets import ChooseOptionInputlet
16from thb.mode import THBEventHandler
19# -- code --
20class Dilemma(Skill):
21 associated_action = None
22 skill_category = ['character', 'passive']
23 target = t_None()
26class DilemmaDamageAction(UserAction):
27 card_usage = 'handover'
29 def apply_action(self):
30 src = self.source
31 tgt = self.target
33 cards = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))
34 g = self.game
35 if cards:
36 self.peer_action = 'card'
37 g.players.exclude(tgt).reveal(cards)
38 migrate_cards(cards, src.cards)
39 else:
40 self.peer_action = 'life'
41 g.process_action(LifeLost(src, tgt, 1))
43 return True
45 def cond(self, cards):
46 if len(cards) != 1: return False
47 card = cards[0]
48 if card.is_card(Skill):
49 return False
51 if card.resides_in.type not in (
52 'cards', 'showncards', 'equips'
53 ): return False
55 return card.suit == Card.DIAMOND
58class DilemmaHealAction(DrawCards):
59 def __init__(self, source, target, amount=2):
60 self.source = source
61 self.target = target
62 self.amount = amount
63 self.back = False
66class DilemmaHandler(THBEventHandler):
67 interested = ['action_after']
68 execute_after = [
69 'DyingHandler',
70 'AyaRoundfanHandler',
71 'NenshaPhoneHandler',
72 ]
74 def handle(self, evt_type, act):
75 if evt_type != 'action_after': return act
76 if not isinstance(act, (Damage, Heal)): return act
78 src = act.source
79 tgt = act.target
80 if tgt.dead: return act
81 if not tgt.has_skill(Dilemma): return act
82 if not src: return act
84 self.dilemma_type = 'negative' if isinstance(act, Damage) else 'positive'
85 g = self.game
86 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
87 return act
89 if isinstance(act, Damage):
90 g.process_action(DilemmaDamageAction(tgt, src))
91 else: # Heal
92 g.process_action(DilemmaHealAction(tgt, src, 1))
94 return act
97class ImperishableNight(TreatAs, Skill):
98 treat_as = SealingArrayCard
99 skill_category = ['character', 'passive']
101 def check(self):
102 g = self.game
103 current = PlayerTurn.get_current(g).target
104 return current is not self.character
107class ImperishableNightHandler(THBEventHandler):
108 interested = ['action_after']
109 card_usage = 'launch'
111 def handle(self, evt_type, act):
112 if evt_type != 'action_after': return act
113 if not isinstance(act, LaunchCard): return act
115 g = self.game
117 card = act.card
118 if not card: return act
119 if not isinstance(card, BasicCard): return act
120 if card.color != Card.RED: return act
122 if card.is_card(VirtualCard):
123 rawcards = VirtualCard.unwrap([card])
124 else:
125 rawcards = [card]
127 if not all(
128 c.resides_in is None or c.resides_in.type == 'droppedcard'
129 for c in rawcards
130 ): return act
132 tgt = act.source
133 self.target = tgt # for ui
135 if tgt.dead: return act
137 current: Optional[Character]
139 try:
140 current = PlayerTurn.get_current(g).target
141 except IndexError:
142 current = None
144 for p in g.players:
145 if p.dead or p is tgt: continue
146 if not p.has_skill(ImperishableNight): continue
147 if p is current: continue
149 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):
150 continue
152 cards = user_choose_cards(self, p, ('cards', 'showncards', 'equips'))
154 if cards:
155 g.players.reveal(cards)
156 skill = skill_wrap(p, [ImperishableNight], cards, {})
157 assert skill_check(skill) # should not fail
158 g.deck.register_vcard(skill)
159 rst = g.process_action(LaunchCard(p, [tgt], skill))
160 assert rst
162 return act
164 def cond(self, cards):
165 if len(cards) != 1: return False
166 card = cards[0]
167 if card.resides_in.type not in (
168 'cards', 'showncards', 'equips'
169 ): return False
170 if 'skill' in card.category: return False
171 if card.color != Card.RED: return False
172 return bool(set(card.category) & {'basic', 'equipment'})
174 def ask_for_action_verify(self, p, cl, tl):
175 tgt = self.target
176 skill = skill_wrap(p, [ImperishableNight], cl, {})
177 return LaunchCard(p, [tgt], skill).can_fire()
180@register_character_to('common')
181class Kaguya(Character):
182 skills = [Dilemma, ImperishableNight]
183 eventhandlers = [DilemmaHandler, ImperishableNightHandler]
184 maxlife = 3