Coverage for thb/characters/seiga.py : 25%
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 ActionStage, ForEach, GenericAction, LaunchCard, LifeLost, PlayerDeath
8from thb.actions import PlayerTurn, UserAction
9from thb.cards.base import Skill
10from thb.cards.classes import AttackCard, AttackCardVitalityHandler, t_None, t_Self
11from thb.characters.base import Character, register_character_to
12from thb.common import CharChoice
13from thb.inputlets import ChooseOptionInputlet
14from thb.mode import THBEventHandler, THBAction
15from thb.thbkof import KOFCharacterSwitchHandler, THBattleKOF
18# -- code --
19class HeterodoxySkipAction(GenericAction):
20 def apply_action(self):
21 return True
24class HeterodoxyHandler(THBEventHandler):
25 interested = ['action_before']
26 execute_before = ['MaidenCostumeHandler']
28 def handle(self, evt_type, act):
29 if evt_type == 'action_before' and ForEach.is_group_effect(act):
30 tgt = act.target
31 if not tgt.has_skill(Heterodoxy): return act
33 g = self.game
34 for a in reversed(g.action_stack):
35 if isinstance(a, HeterodoxyAction):
36 break
37 else:
38 return act
40 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
41 return act
43 act.cancelled = True
44 g.process_action(HeterodoxySkipAction(tgt, tgt))
46 return act
49class HeterodoxyAction(UserAction):
50 def apply_action(self):
51 g = self.game
52 sk = self.associated_card
53 assert isinstance(sk, Heterodoxy)
54 card = sk.associated_cards[0]
56 src = self.source
57 victim = self.target
58 tgts = self.target_list[1:]
60 g.players.reveal(card)
62 if card.is_card(AttackCard):
63 src.tags['vitality'] -= 1
65 # XXX: Use card owned by other
66 lc = LaunchCard(victim, tgts, card)
68 g = self.game
69 g.process_action(lc)
71 return True
73 def is_valid(self):
74 src = self.source
75 sk = self.associated_card
76 assert isinstance(sk, Heterodoxy)
77 card = sk.associated_cards[0]
78 if card.is_card(AttackCard) and src.tags['vitality'] < 1:
79 if not AttackCardVitalityHandler.is_disabled(src):
80 return False
82 if card.usage != 'launch':
83 return False
85 victim = self.target
86 tgts = self.target_list[1:]
87 lc = LaunchCard(victim, tgts, card)
88 return lc.can_fire()
91class Heterodoxy(Skill):
92 no_drop = True
93 associated_action = HeterodoxyAction
94 skill_category = ['character', 'active']
95 usage = 'handover'
97 def check(self):
98 cl = self.associated_cards
99 return (
100 cl and len(cl) == 1 and
101 cl[0].resides_in.type in ('cards', 'showncards') and
102 not cl[0].is_card(Skill) and
103 getattr(cl[0], 'associated_action', None)
104 )
106 def target(self, src, tl):
107 cl = self.associated_cards
108 if not cl: return ([], False)
109 c = cl[0]
110 tname = c.target.__name__
112 tl = [t for t in tl if not t.dead]
114 if not tl: return [], False
115 if tl[0] is self.character: return [], False
117 if tname in ('t_Self', 't_All', 't_AllInclusive'):
118 return tl[-1:], True
119 else:
120 _tl, valid = c.target(tl[0], tl[1:])
121 return [tl[0]] + _tl, valid
124class Summon(Skill):
125 associated_action = None
126 skill_category = ['character', 'passive', 'once']
127 target = t_None()
130class SummonAction(UserAction):
132 def apply_action(self):
133 src, tgt = self.source, self.target
135 skills = tgt.skills
136 skills = [s for s in skills if 'character' in s.skill_category]
137 skills = [s for s in skills if 'once' not in s.skill_category]
138 skills = [s for s in skills if 'awake' not in s.skill_category]
140 if not skills: return False
142 mapping = self.mapping = {s.__name__: s for s in skills}
143 names = list(sorted(mapping.keys()))
145 g = self.game
146 choice = g.user_input([src], ChooseOptionInputlet(self, names)) or names[0]
148 src.tags['summon_used'] = True
149 src.skills.append(mapping[choice])
150 self.choice = mapping[choice]
152 return True
155class SummonHandler(THBEventHandler):
156 interested = ['action_apply']
157 execute_after = ['DeathHandler']
159 def handle(self, evt_type, act):
160 if evt_type == 'action_apply' and isinstance(act, PlayerDeath):
161 g = self.game
162 p = PlayerTurn.get_current(g).target
163 if p is act.target: return act
164 if not p.has_skill(Summon): return act
165 if p.tags['summon_used']: return act
166 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))): return act
168 g.process_action(SummonAction(p, act.target))
170 return act
173class SummonKOFAction(UserAction):
174 game: THBattleKOF
176 def apply_action(self):
177 # WHOLE BUNCH OF MEGA HACK
178 old = self.target
179 g = self.game
180 old_life, maxlife_delta = old.life, old.maxlife - old.__class__.maxlife
182 ActionStage.force_break(g)
184 current = PlayerTurn.get_current(g).target
185 assert current is old
187 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler)
188 assert handler
189 tgt = handler.switch(old)
191 tgt.life = old_life
192 tgt.maxlife += maxlife_delta
194 for l in ('cards', 'showncards', 'equips', 'fatetell', 'special'):
195 s, t = getattr(old, l), getattr(tgt, l)
196 for i in list(s):
197 i.move_to(t)
199 for s in old.skills:
200 if 'character' not in s.skill_category:
201 tgt.skills.append(s)
203 for act in g.action_stack:
204 # Meh... good enough
205 if not isinstance(act, THBAction):
206 continue
208 if act.source is old:
209 act.source = tgt
211 if act.target is old:
212 act.target = tgt
214 if isinstance(act, LaunchCard):
215 act.target_list = [
216 tgt if p is old else p
217 for p in act.target_list
218 ]
220 tgt.tags = old.tags
222 g.chosen[tgt.player].append(CharChoice(old.__class__))
224 if tgt.life > tgt.maxlife:
225 g.process_action(LifeLost(tgt, tgt, tgt.life - tgt.maxlife))
227 self.transition = [old, tgt]
229 g.emit_event('character_debut', (old, tgt))
231 return True
234class SummonKOFCollect(UserAction):
235 game: THBattleKOF
237 def apply_action(self):
238 src, tgt = self.source, self.target
239 g = self.game
240 g.chosen[src.player].append(CharChoice(tgt.__class__))
241 return True
244class SummonKOFHandler(THBEventHandler):
245 game: THBattleKOF
246 interested = ['action_apply']
248 def handle(self, evt_type, act):
249 if evt_type == 'action_apply' and isinstance(act, PlayerDeath):
250 g = self.game
251 src, tgt = act.source, act.target
252 p = g.get_opponent(tgt)
254 if not (src is p and p.has_skill(SummonKOF)):
255 return act
257 g.process_action(SummonKOFCollect(p, tgt))
259 return act
262class SummonKOF(Skill):
263 associated_action = SummonKOFAction
264 skill_category = ['character', 'active']
265 target = t_Self()
267 def check(self):
268 cl = self.associated_cards
269 return len(cl) == 0
272@register_character_to('common', '-kof')
273class Seiga(Character):
274 skills = [Heterodoxy, Summon]
275 eventhandlers = [HeterodoxyHandler, SummonHandler]
276 maxlife = 4
279@register_character_to('kof')
280class SeigaKOF(Character):
281 skills = [SummonKOF]
282 eventhandlers = [SummonKOFHandler]
283 maxlife = 4