Coverage for thb/characters/remilia.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 Sequence, TYPE_CHECKING
7# -- third party --
8# -- own --
9from thb.actions import ActionShootdown, Damage, GenericAction, LaunchCard, PrepareStage, UserAction
10from thb.cards.base import Card, DummyCard, Skill, t_None
11from thb.cards.classes import Attack, AttackCard, Heal, InevitableAttack
12from thb.characters.base import Character, register_character_to
13from thb.inputlets import ChooseOptionInputlet
14from thb.mode import THBEventHandler
16# -- typing --
17if TYPE_CHECKING:
18 from thb.thbrole import THBattleRole # noqa: F401
21# -- code --
22class SpearTheGungnir(Skill):
23 associated_action = None
24 skill_category = ['character', 'passive']
25 target = t_None()
28class SpearTheGungnirAction(GenericAction):
29 def __init__(self, act):
30 self.act = act
31 self.source = act.source
32 self.target = act.target
34 def apply_action(self):
35 self.act.__class__ = InevitableAttack
36 return True
39class SpearTheGungnirHandler(THBEventHandler):
40 interested = ['action_before']
41 execute_before = ['ScarletRhapsodySwordHandler']
42 execute_after = ['HakuroukenEffectHandler', 'HouraiJewelHandler']
44 def handle(self, evt_type, act):
45 if evt_type == 'action_before' and isinstance(act, Attack):
46 src = act.source
47 if not src.has_skill(SpearTheGungnir): return act
48 if isinstance(act, InevitableAttack): return act
50 tgt = act.target
52 while True:
53 if tgt.life > src.life: break
54 if len(tgt.cards) + len(tgt.showncards) < len(src.cards) + len(src.showncards): break
55 return act
57 g = self.game
58 if g.user_input([act.source], ChooseOptionInputlet(self, (False, True))):
59 g.process_action(SpearTheGungnirAction(act))
61 return act
64class VampireKiss(Skill):
65 associated_action = None
66 skill_category = ['character', 'passive', 'compulsory']
67 target = t_None()
70class VampireKissAction(GenericAction):
71 def apply_action(self):
72 return self.game.process_action(
73 Heal(self.target, self.source)
74 )
77class VampireKissHandler(THBEventHandler):
78 interested = ['action_apply', 'calcdistance']
80 def handle(self, evt_type, act):
81 if evt_type == 'action_apply' and isinstance(act, Damage):
82 src, tgt = act.source, act.target
83 if not (src and src.has_skill(VampireKiss)): return act
84 if src.life >= src.maxlife: return act
85 g = self.game
86 pact = g.action_stack[-1]
87 if not isinstance(pact, Attack): return act
88 card = pact.associated_card
89 if (not card) or card.color != Card.RED: return act
90 g.process_action(VampireKissAction(src, tgt))
92 elif evt_type == 'calcdistance':
93 src, card, dist = act
95 if not card.is_card(AttackCard): return act
96 if not src.has_skill(VampireKiss): return act
97 if card.color != Card.RED: return act
99 for p in dist:
100 dist[p] -= 99999
102 return act
105class ScarletMistAttackLimit(ActionShootdown):
106 pass
109class ScarletMistHandler(THBEventHandler):
110 interested = [
111 'action_after',
112 'action_apply',
113 'action_shootdown',
114 'post_calcdistance',
115 ]
117 def handle(self, evt_type, act):
118 if evt_type == 'action_shootdown' and isinstance(act, LaunchCard):
119 if act.bypass_check: return act
120 c = act.card
121 if not c.is_card(AttackCard): return act
123 src, tgt = act.source, act.target
124 if not src.tags['scarlet_mist'] == 'nerf':
125 return act
127 g = self.game
128 dist = LaunchCard.calc_raw_distance(g, src, DummyCard())
129 if dist[tgt] > 1:
130 raise ScarletMistAttackLimit
132 elif evt_type == 'post_calcdistance':
133 src, c, dist = act
135 if not c.is_card(AttackCard):
136 return act
138 if src.tags['scarlet_mist'] != 'buff':
139 return act
141 for k in dist:
142 dist[k] = 0
144 elif evt_type == 'action_after' and isinstance(act, Damage):
145 src = act.source
146 if not (src and src.tags['scarlet_mist'] == 'buff'): return act
147 if src.life >= src.maxlife: return act
149 g = self.game
150 pact = g.action_stack[-1]
151 if not isinstance(pact, Attack): return act
152 if not pact.associated_card: return act
154 g.process_action(Heal(src, src, act.amount))
156 elif evt_type == 'action_apply' and isinstance(act, PrepareStage):
157 tgt = act.target
158 if not tgt.has_skill(ScarletMist): return act
159 if not tgt.tags['scarlet_mist']: return act
160 g = self.game
161 g.process_action(ScarletMistEndAction(tgt, tgt))
163 return act
166class ScarletMistAction(UserAction):
167 def apply_action(self):
168 src, tl = self.source, self.target_list
169 src.tags['scarlet_mist_used'] = True
170 g = self.game
171 for p in g.players:
172 p.tags['scarlet_mist'] = 'nerf'
173 for p in tl:
174 p.tags['scarlet_mist'] = 'buff'
176 return True
178 def is_valid(self):
179 src = self.source
180 return not src.tags['scarlet_mist_used']
183class ScarletMistEndAction(GenericAction):
184 def apply_action(self):
185 g = self.game
186 for p in g.players:
187 p.tags['scarlet_mist'] = False
189 return True
192class ScarletMist(Skill):
193 associated_action = ScarletMistAction
194 skill_category = ['character', 'active', 'once', 'boss']
195 game: THBattleRole
197 def check(self) -> bool:
198 return not len(self.associated_cards)
200 def target(self, src: Character, tl: Sequence[Character]):
201 g = self.game
202 from thb.thbrole import THBRoleRole
203 n = sum(i == THBRoleRole.ACCOMPLICE for i in g.roles.values())
204 n -= sum(ch.dead and g.roles[ch.player] == THBRoleRole.ACCOMPLICE for ch in g.players)
206 tl = [t for t in tl if not t.dead]
207 try:
208 tl.remove(src)
209 except ValueError:
210 pass
212 tl.insert(0, src)
213 return (tl[:n+1], bool(len(tl)))
216@register_character_to('common', 'boss')
217class Remilia(Character):
218 skills = [SpearTheGungnir, VampireKiss]
219 boss_skills = [ScarletMist]
220 eventhandlers = [SpearTheGungnirHandler, VampireKissHandler, ScarletMistHandler]
221 maxlife = 4