Coverage for thb/characters/meirin.py : 35%
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 DropCards, GenericAction, MaxLifeChange, PrepareStage, random_choose_card
8from thb.cards.base import DummyCard, Skill, t_None
9from thb.cards.classes import AttackCard, BaseAttack, GrazeCard, LaunchGraze, TreatAs
10from thb.characters.base import Character, register_character_to
11from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet
12from thb.mode import THBEventHandler
15# -- code --
16class LoongPunch(Skill):
17 associated_action = None
18 skill_category = ['character', 'passive']
19 target = t_None()
22class Taichi(TreatAs, Skill):
23 skill_category = ['character', 'active']
25 @property
26 def treat_as(self):
27 cl = self.associated_cards
28 if not cl: return DummyCard
29 c = cl[0]
30 if c.is_card(GrazeCard):
31 return AttackCard
32 if c.is_card(AttackCard):
33 return GrazeCard
34 return DummyCard
36 def check(self):
37 cl = self.associated_cards
38 if not cl or len(cl) != 1: return False
39 c = cl[0]
40 if not (c.is_card(AttackCard) or c.is_card(GrazeCard)): return False
41 return c.resides_in is not None and c.resides_in.type in (
42 'cards', 'showncards',
43 )
46class RiverBehind(Skill):
47 associated_action = None
48 skill_category = ['character', 'passive', 'awake']
49 target = t_None()
52class LoongPunchAction(GenericAction):
53 def __init__(self, source, target, _type):
54 self.source = source
55 self.target = target
56 self.type = _type
58 def apply_action(self):
59 g = self.game
60 src, tgt = self.source, self.target
61 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards')))
62 c = c or random_choose_card(g, [tgt.cards, tgt.showncards])
63 if not c: return False
64 g.players.exclude(tgt).reveal(c)
65 g.process_action(DropCards(src, tgt, [c]))
66 return True
69class LoongPunchHandler(THBEventHandler):
70 interested = ['action_after']
71 execute_after = ['DeathSickleHandler']
73 def handle(self, evt_type, act):
74 if evt_type == 'action_after' and isinstance(act, LaunchGraze):
75 if not act.succeeded: return act
76 g = self.game
77 pact = g.action_stack[-1]
78 if not isinstance(pact, BaseAttack): return act
79 self.do_effect(pact.source, pact.target, 'attack')
80 self.do_effect(pact.target, pact.source, 'graze')
82 return act
84 def do_effect(self, src, tgt, _type):
85 if not src.has_skill(LoongPunch): return
86 if not (tgt.cards or tgt.showncards): return
87 g = self.game
88 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))): return
90 g.process_action(LoongPunchAction(src, tgt, _type))
93class RiverBehindAwake(GenericAction):
94 def apply_action(self):
95 tgt = self.target
96 assert tgt.has_skill(RiverBehind)
97 tgt.skills.remove(RiverBehind)
98 tgt.skills.append(Taichi)
99 g = self.game
100 g.process_action(MaxLifeChange(tgt, tgt, -1))
101 return True
104class RiverBehindHandler(THBEventHandler):
105 interested = ['action_apply']
107 def handle(self, evt_type, act):
108 if evt_type == 'action_apply' and isinstance(act, PrepareStage): 108 ↛ 109line 108 didn't jump to line 109, because the condition on line 108 was never true
109 tgt = act.target
110 if not tgt.has_skill(RiverBehind): return act
111 g = self.game
112 if tgt.life <= 2 and tgt.life <= min(p.life for p in g.players if not p.dead):
113 g.process_action(RiverBehindAwake(tgt, tgt))
114 return act
117@register_character_to('common')
118class Meirin(Character):
119 skills = [LoongPunch, RiverBehind]
120 eventhandlers = [RiverBehindHandler, LoongPunchHandler]
121 maxlife = 4