예제 #1
0
    class RoomControlPanel(Control):
        def __init__(self, parent=None):
            Control.__init__(self, parent=parent, **r2d((0, 0, 820, 720)))
            self.btn_getready = Button(
                parent=self, caption=u'准备', **r2d((360, 80, 100, 35))
            )

            self.ready = False

            l = []

            class MyPP(PlayerPortrait):
                # this class is INTENTIONALLY put here
                # to make cached avatars get gc'd
                cached_avatar = {}

            for x, y, color in parent.ui_class.portrait_location:
                l.append(MyPP('NONAME', parent=self, x=x, y=y, color=color))
            self.portraits = l

            @self.btn_getready.event
            def on_click():
                if self.ready:
                    Executive.call('cancel_ready', ui_message, [])
                    self.ready = False
                    self.btn_getready.caption = u'准备'
                    self.btn_getready.update()
                else:
                    Executive.call('get_ready', ui_message, [])
                    #self.btn_getready.state = Button.DISABLED
                    self.ready = True
                    self.btn_getready.caption = u'取消准备'
                    self.btn_getready.update()

        def draw(self):
            self.draw_subcontrols()

        def on_message(self, _type, *args):
            if _type == 'player_change':
                self.update_portrait(args[0])
            elif _type == 'kick_request':
                u1, u2, count = args[0]
                self.parent.chat_box.append(
                    u'|B|R>> |c0000ffff%s|r希望|c0000ffff|B%s|r离开游戏,已有%d人请求\n' % (
                        u1[1], u2[1], count
                    )
                )
            elif _type == 'game_joined':
                self.ready = False
                self.btn_getready.caption = u'准备'
                self.btn_getready.state = Button.NORMAL

        def update_portrait(self, pl):
            def players():
                return {
                    p.account.username
                    for p in self.portraits
                    if p.account
                }

            orig_players = players()
            full = True

            for i, p in enumerate(pl):
                accdata = p['account']
                acc = Account.parse(accdata) if accdata else None
                if not accdata: full = False

                port = self.portraits[i]
                port.account = acc
                port.ready = (p['state'] == 'ready')

                port.update()

            curr_players = players()

            for player in (orig_players - curr_players):
                self.parent.chat_box.append(
                    u'|B|R>> |r玩家|c0000ffff|B%s|r已离开游戏\n' % player
                )

            for player in (curr_players - orig_players):
                self.parent.chat_box.append(
                    u'|B|R>> |r玩家|c0000ffff|B%s|r已进入游戏\n' % player
                )

            if not self.ready and full and orig_players != curr_players:
                from utils import notify
                notify(u'东方符斗祭 - 满员提醒', u'房间已满员,请准备。')
예제 #2
0
파일: view.py 프로젝트: AojiaoZero/thbattle
class THBattleUI(Control):
    portrait_location = [
        (60, 300, Colors.blue),
        (250, 450, Colors.orange),
        (450, 450, Colors.blue),
        (640, 300, Colors.orange),
        (450, 150, Colors.blue),
        (250, 150, Colors.orange),
    ]

    gcp_location = [
        (3, 1, 'me', Colors.blue),
        (669, 280, 'left', Colors.orange),
        (155+180+180, 520, 'bottom', Colors.blue),
        (155+180, 520, 'bottom', Colors.orange),
        (155, 520, 'bottom', Colors.blue),
        (3, 280, 'right', Colors.orange),
    ]

    def __init__(self, game, *a, **k):
        self.game = game
        game.event_observer = UIEventHook

        Control.__init__(self, can_focus=True, *a, **k)

        self.keystrokes = '\x00'
        self.char_portraits = None

        self.deck_indicator = DeckIndicator(
            parent=self, x=30, y=680, width=50, height=25,
        )

        self.handcard_area = HandCardArea(
            parent=self, x=238, y=9, zindex=3,
            width=93*5+42, height=145,
        )

        self.deck_area = PortraitCardArea(
            parent=self, width=1, height=1,
            x=self.width//2, y=self.height//2, zindex=4,
        )

        self.btn_afk = Button(
            parent=self, caption=u'让⑨帮你玩', zindex=1,
            color=Colors.blue,
            **r2d((730, 640, 75, 25))
        )

        self.gameintro_icon = GameIntroIcon(
            parent=self, game=game,
            **r2d((780, 610, 25, 25))
        )

        self.afk = False

        @self.btn_afk.event
        def on_click():
            v = not self.afk
            self.afk = v
            self.btn_afk.color = (Colors.blue, Colors.orange)[v]
            self.btn_afk.update()

        @self.handcard_area.event
        def on_selection_change():
            self.dispatch_event('on_selection_change')

        self.dropcard_area = DropCardArea(
            parent=self, x=0, y=324, zindex=3,
            width=820, height=125,
        )

        class Animations(pyglet.graphics.Batch, Control):
            def __init__(self, **k):
                pyglet.graphics.Batch.__init__(self)
                Control.__init__(
                    self, x=0, y=0,
                    width=0, height=0, zindex=2,
                    **k
                )

            def hit_test(self, x, y):
                return False

        self.animations = Animations(parent=self)
        self.selecting_player = 0

    def init(self):
        ports = self.char_portraits = [
            GameCharacterPortrait(parent=self, color=color, x=x, y=y, tag_placement=tp)
            for x, y, tp, color in self.gcp_location[:len(self.game.players)]
        ]

        pl = self.game.players
        shift = pl.index(self.game.me)
        for i, c in enumerate(ports):
            p = pl[(shift + i) % self.game.n_persons]
            c.player = p
            c.update()

        ports[0].equipcard_area.selectable = True  # it's TheChosenOne

        self.begin_select_player()
        self.end_select_player()
        self.skill_box = SkillSelectionBox(
            parent=self, x=161, y=9, width=70, height=22*6-4
        )

        SoundManager.switch_bgm(gres.bgm_game)

        self.more_init()

    def more_init(self):
        pass

    def player2portrait(self, p):
        from gamepack.thb.characters.baseclasses import Character
        if isinstance(p, Character):
            p = p.player

        for port in self.char_portraits:
            if port.player == p:
                break
        else:
            raise ValueError(p)
        return port

    def update_skillbox(self):
        g = self.game
        skills = getattr(g.me, 'skills', None)
        if skills is None:
            # before girl chosen
            return

        skills = [
            (i, s, s.ui_meta.clickable(g))
            for i, s in enumerate(skills)
            if not getattr(s.ui_meta, 'no_display', False)
        ]

        skills.sort(key=lambda i: -i[2])

        self.skill_box.set_skills(
            (s.ui_meta.name, i, e) for i, s, e in skills
        )

    PORT_UPDATE_MESSAGES = {
        'evt_game_begin',
        'evt_switch_character',
    }

    def update_portraits(self):
        for port in self.char_portraits:
            port.update()

    def on_message(self, _type, *args):
        if _type == 'evt_action_before' and isinstance(args[0], actions.PlayerTurn):
            self.current_turn = args[0].target

        elif _type == 'player_change':
            for i, pd in enumerate(args[0]):
                p = self.game.players[i]
                port = self.player2portrait(p)
                port.dropped = (pd['state'] in ('dropped', 'fleed'))
                port.fleed = (pd['state'] == 'fleed')
                port.update()

        elif _type in self.PORT_UPDATE_MESSAGES:
            self.update_portraits()

        elif _type == 'evt_action_after':
            act = args[0]
            meta = getattr(act, 'ui_meta', None)
            if meta and getattr(meta, 'update_portrait', None):
                pl = set()
                if act.source:
                    pl.add(act.source)

                if hasattr(act, 'target_list'):
                    pl.update(act.target_list)
                elif act.target:
                    pl.add(act.target)

                for p in pl:
                    self.player2portrait(p).update()

        self.more_on_message(_type, args)

        if _type.startswith('evt_'):
            effects.handle_event(self, _type[4:], args[0])
            inputs.handle_event(self, _type[4:], args[0])

    def more_on_message(self, _type, args):
        pass

    def on_text(self, text):
        # The easter egg
        ks = self.keystrokes
        ks = (ks + text)[:40]
        self.keystrokes = ks

        from gamepack.thb.characters import characters as chars

        for c in chars:
            try:
                alter = c.ui_meta.figure_image_alter
            except:
                continue

            for i in xrange(len(ks)):
                if alter.decrypt(ks[-i:]):
                    SoundManager.play(cres.sound.input)

    def draw(self):
        self.draw_subcontrols()

    def ray(self, f, t):
        if f == t: return
        sp = self.player2portrait(f)
        dp = self.player2portrait(t)
        x0, y0 = sp.x + sp.width/2, sp.y + sp.height/2
        x1, y1 = dp.x + dp.width/2, dp.y + dp.height/2
        Ray(x0, y0, x1, y1, parent=self, zindex=10)

    def prompt(self, s):
        self.prompt_raw(u'|B|cff0000ff>> |r' + unicode(s) + u'\n')

    def prompt_raw(self, s):
        self.parent.events_box.append(s)

    def begin_select_player(self, disables=[]):
        #if self.selecting_player: return
        self.selecting_player = True
        #self.selected_players = []
        for p in self.game.players:
            port = self.player2portrait(p)

            if p in disables:
                port.disabled = True
                port.selected = False
                try:
                    self.selected_players.remove(p)
                except ValueError:
                    pass
            else:
                port.disabled = False

    def get_selected_players(self):
        return self.selected_players

    def set_selected_players(self, players):
        for p in self.char_portraits:
            p.selected = False

        for p in players:
            self.player2portrait(p).selected = True

        self.selected_players = players[:]

    def end_select_player(self):
        #if not self.selecting_player: return
        self.selecting_player = False
        self.selected_players = []
        for p in self.char_portraits:
            p.selected = False
            p.disabled = False

    def get_selected_cards(self):
        return [
            cs.associated_card
            for cs in self.handcard_area.cards
            if cs.hca_selected
        ] + [
            cs.associated_card
            for cs in self.player2portrait(self.game.me).equipcard_area.cards
            if cs.selected
        ]

    def get_selected_skills(self):
        skills = self.game.me.skills
        return sorted([
            skills[i] for i in self.skill_box.get_selected_index()
        ], key=lambda s: s.sort_index)

    def on_mouse_click(self, x, y, button, modifier):
        c = self.control_frompoint1_recursive(x, y)
        if isinstance(c, GameCharacterPortrait) and self.selecting_player and not c.disabled:
            char = c.character
            if not char: return True
            sel = c.selected
            psel = self.selected_players
            if sel:
                c.selected = False
                psel.remove(char)
            else:
                c.selected = True
                psel.append(char)
            self.dispatch_event('on_selection_change')
        return True

    def get_game_screen(self):
        assert self.parent
        return self.parent

    @staticmethod
    def show_result(g):
        ResultPanel(g, parent=Overlay.cur_overlay)
예제 #3
0
파일: screens.py 프로젝트: hycxa/thbattle
    class RoomControlPanel(Control):
        def __init__(self, parent=None):
            Control.__init__(self, parent=parent, **r2d((0, 0, 820, 720)))
            self.btn_getready = Button(parent=self, caption=u"准备", **r2d((360, 80, 100, 35)))

            self.btn_invite = Button(parent=self, caption=u"邀请", **r2d((360, 40, 100, 35)))

            self.ready = False

            l = []

            class MyPP(PlayerPortrait):
                # this class is INTENTIONALLY put here
                # to make cached avatars get gc'd
                cached_avatar = {}

            for x, y, color in parent.ui_class.portrait_location:
                l.append(MyPP("NONAME", parent=self, x=x, y=y, color=color))
            self.portraits = l

            @self.btn_getready.event
            def on_click():
                if self.ready:
                    Executive.call("cancel_ready", ui_message, [])
                    self.ready = False
                    self.btn_getready.caption = u"准备"
                    self.btn_getready.update()
                else:
                    Executive.call("get_ready", ui_message, [])
                    # self.btn_getready.state = Button.DISABLED
                    self.ready = True
                    self.btn_getready.caption = u"取消准备"
                    self.btn_getready.update()

            @self.btn_invite.event  # noqa
            def on_click():
                GameScreen.InvitePanel(self.parent.game.gameid, parent=self)

        def draw(self):
            self.draw_subcontrols()

        def on_message(self, _type, *args):
            if _type == "player_change":
                self.update_portrait(args[0])
            elif _type == "kick_request":
                u1, u2, count = args[0]
                self.parent.chat_box.append(
                    u"|B|R>> |c0000ffff%s|r希望|c0000ffff|B%s|r离开游戏,已有%d人请求\n" % (u1[1], u2[1], count)
                )
            elif _type == "game_joined":
                self.ready = False
                self.btn_getready.caption = u"准备"
                self.btn_getready.state = Button.NORMAL

        def update_portrait(self, pl):
            def players():
                return {p.account.username for p in self.portraits if p.account}

            orig_players = players()
            full = True

            for i, p in enumerate(pl):
                accdata = p["account"]
                acc = Account.parse(accdata) if accdata else None
                if not accdata:
                    full = False

                port = self.portraits[i]
                port.account = acc
                port.ready = p["state"] == "ready"

                port.update()

            curr_players = players()

            for player in orig_players - curr_players:
                self.parent.chat_box.append(u"|B|R>> |r玩家|c0000ffff|B%s|r已离开游戏\n" % player)

            for player in curr_players - orig_players:
                self.parent.chat_box.append(u"|B|R>> |r玩家|c0000ffff|B%s|r已进入游戏\n" % player)

            if not self.ready and full and orig_players != curr_players:
                from utils import notify

                notify(u"东方符斗祭 - 满员提醒", u"房间已满员,请准备。")
예제 #4
0
    class RoomControlPanel(Control):
        def __init__(self, parent=None):
            Control.__init__(self, parent=parent, **r2d((0, 0, 820, 720)))
            self.btn_getready = Button(
                parent=self, caption=u'准备', **r2d((360, 80, 100, 35))
            )

            self.btn_invite = Button(
                parent=self, caption=u'邀请', **r2d((360, 40, 100, 35))
            )

            self.ready = False

            l = []

            class MyPP(PlayerPortrait):
                # this class is INTENTIONALLY put here
                # to make cached avatars get gc'd
                cached_avatar = {}

            for x, y, color in parent.ui_class.portrait_location:
                l.append(MyPP('NONAME', parent=self, x=x, y=y, color=color))
            self.portraits = l

            @self.btn_getready.event
            def on_click():
                if self.ready:
                    Executive.call('cancel_ready', ui_message, [])
                    self.ready = False
                    self.btn_getready.caption = u'准备'
                    self.btn_getready.update()
                else:
                    Executive.call('get_ready', ui_message, [])
                    #self.btn_getready.state = Button.DISABLED
                    self.ready = True
                    self.btn_getready.caption = u'取消准备'
                    self.btn_getready.update()

            @self.btn_invite.event  # noqa
            def on_click():
                GameScreen.InvitePanel(self.parent.game.gameid, parent=self)

        def draw(self):
            self.draw_subcontrols()

        def on_message(self, _type, *args):
            if _type == 'player_change':
                self.update_portrait(args[0])
            elif _type == 'kick_request':
                u1, u2, count = args[0]
                self.parent.chat_box.append(
                    u'|B|R>> |c0000ffff%s|r希望|c0000ffff|B%s|r离开游戏,已有%d人请求\n' % (
                        u1[1], u2[1], count
                    )
                )
            elif _type == 'game_joined':
                self.ready = False
                self.btn_getready.caption = u'准备'
                self.btn_getready.state = Button.NORMAL

        def update_portrait(self, pl):
            def players():
                return {
                    p.account.username
                    for p in self.portraits
                    if p.account
                }

            orig_players = players()
            full = True

            for i, p in enumerate(pl):
                accdata = p['account']
                acc = Account.parse(accdata) if accdata else None
                if not accdata: full = False

                port = self.portraits[i]
                port.account = acc
                port.ready = (p['state'] == 'ready')

                port.update()

            curr_players = players()

            for player in (orig_players - curr_players):
                self.parent.chat_box.append(
                    u'|B|R>> |r玩家|c0000ffff|B%s|r已离开游戏\n' % player
                )

            for player in (curr_players - orig_players):
                self.parent.chat_box.append(
                    u'|B|R>> |r玩家|c0000ffff|B%s|r已进入游戏\n' % player
                )

            if not self.ready and full and orig_players != curr_players:
                from utils import notify
                notify(u'东方符斗祭 - 满员提醒', u'房间已满员,请准备。')
예제 #5
0
class THBattleUI(Control):
    portrait_location = [
        (60, 300, Colors.blue),
        (250, 450, Colors.orange),
        (450, 450, Colors.blue),
        (640, 300, Colors.orange),
        (450, 150, Colors.blue),
        (250, 150, Colors.orange),
    ]

    gcp_location = [
        (3, 1, 'me', Colors.blue),
        (669, 280, 'left', Colors.orange),
        (155 + 180 + 180, 520, 'bottom', Colors.blue),
        (155 + 180, 520, 'bottom', Colors.orange),
        (155, 520, 'bottom', Colors.blue),
        (3, 280, 'right', Colors.orange),
    ]

    def __init__(self, game, *a, **k):
        self.game = game
        game.event_observer = UIEventHook

        Control.__init__(self, can_focus=True, *a, **k)

        self.keystrokes = '\x00'
        self.char_portraits = None

        self.deck_indicator = DeckIndicator(
            parent=self,
            x=30,
            y=680,
            width=50,
            height=25,
        )

        self.handcard_area = HandCardArea(
            parent=self,
            x=238,
            y=9,
            zindex=3,
            width=93 * 5 + 42,
            height=145,
        )

        self.deck_area = PortraitCardArea(
            parent=self,
            width=1,
            height=1,
            x=self.width // 2,
            y=self.height // 2,
            zindex=4,
        )

        self.btn_afk = Button(parent=self,
                              caption=u'让⑨帮你玩',
                              zindex=1,
                              color=Colors.blue,
                              **r2d((730, 640, 75, 25)))

        self.gameintro_icon = GameIntroIcon(parent=self,
                                            game=game,
                                            **r2d((780, 610, 25, 25)))

        self.afk = False

        @self.btn_afk.event
        def on_click():
            v = not self.afk
            self.afk = v
            self.btn_afk.color = (Colors.blue, Colors.orange)[v]
            self.btn_afk.update()

        @self.handcard_area.event
        def on_selection_change():
            self.dispatch_event('on_selection_change')

        self.dropcard_area = DropCardArea(
            parent=self,
            x=0,
            y=324,
            zindex=3,
            width=820,
            height=125,
        )

        class Animations(pyglet.graphics.Batch, Control):
            def __init__(self, **k):
                pyglet.graphics.Batch.__init__(self)
                Control.__init__(self,
                                 x=0,
                                 y=0,
                                 width=0,
                                 height=0,
                                 zindex=2,
                                 **k)

            def hit_test(self, x, y):
                return False

        self.animations = Animations(parent=self)
        self.selecting_player = 0

    def init(self):
        ports = self.char_portraits = [
            GameCharacterPortrait(parent=self,
                                  color=color,
                                  x=x,
                                  y=y,
                                  tag_placement=tp)
            for x, y, tp, color in self.gcp_location[:len(self.game.players)]
        ]

        pl = self.game.players
        shift = pl.index(self.game.me)
        for i, c in enumerate(ports):
            p = pl[(shift + i) % self.game.n_persons]
            c.player = p
            c.update()

        ports[0].equipcard_area.selectable = True  # it's TheChosenOne

        self.begin_select_player()
        self.end_select_player()
        self.skill_box = SkillSelectionBox(parent=self,
                                           x=161,
                                           y=9,
                                           width=70,
                                           height=22 * 6 - 4)

        SoundManager.switch_bgm(gres.bgm_game)

        self.more_init()

    def more_init(self):
        pass

    def player2portrait(self, p):
        from gamepack.thb.characters.baseclasses import Character
        if isinstance(p, Character):
            p = p.player

        for port in self.char_portraits:
            if port.player == p:
                break
        else:
            raise ValueError(p)
        return port

    def update_skillbox(self):
        g = self.game
        skills = getattr(g.me, 'skills', None)
        if skills is None:
            # before girl chosen
            return

        skills = [(i, s, s.ui_meta.clickable(g)) for i, s in enumerate(skills)
                  if not getattr(s.ui_meta, 'no_display', False)]

        skills.sort(key=lambda i: -i[2])

        self.skill_box.set_skills((s.ui_meta.name, i, e) for i, s, e in skills)

    PORT_UPDATE_MESSAGES = {
        'evt_game_begin',
        'evt_switch_character',
    }

    def update_portraits(self):
        for port in self.char_portraits:
            port.update()

    def on_message(self, _type, *args):
        if _type == 'evt_action_before' and isinstance(args[0],
                                                       actions.PlayerTurn):
            self.current_turn = args[0].target

        elif _type == 'player_change':
            for i, pd in enumerate(args[0]):
                p = self.game.players[i]
                port = self.player2portrait(p)
                port.dropped = (pd['state'] in ('dropped', 'fleed'))
                port.fleed = (pd['state'] == 'fleed')
                port.update()

        elif _type in self.PORT_UPDATE_MESSAGES:
            self.update_portraits()

        elif _type == 'evt_action_after':
            act = args[0]
            meta = getattr(act, 'ui_meta', None)
            if meta and getattr(meta, 'update_portrait', None):
                pl = set()
                if act.source:
                    pl.add(act.source)

                if hasattr(act, 'target_list'):
                    pl.update(act.target_list)
                elif act.target:
                    pl.add(act.target)

                for p in pl:
                    self.player2portrait(p).update()

        self.more_on_message(_type, args)

        if _type.startswith('evt_'):
            effects.handle_event(self, _type[4:], args[0])
            inputs.handle_event(self, _type[4:], args[0])

    def more_on_message(self, _type, args):
        pass

    def on_text(self, text):
        # The easter egg
        ks = self.keystrokes
        ks = (ks + text)[:40]
        self.keystrokes = ks

        from gamepack.thb.characters import characters as chars

        for c in chars:
            try:
                alter = c.ui_meta.figure_image_alter
            except:
                continue

            for i in xrange(len(ks)):
                if alter.decrypt(ks[-i:]):
                    SoundManager.play(cres.sound.input)

    def draw(self):
        self.draw_subcontrols()

    def ray(self, f, t):
        if f == t: return
        sp = self.player2portrait(f)
        dp = self.player2portrait(t)
        x0, y0 = sp.x + sp.width / 2, sp.y + sp.height / 2
        x1, y1 = dp.x + dp.width / 2, dp.y + dp.height / 2
        Ray(x0, y0, x1, y1, parent=self, zindex=10)

    def prompt(self, s):
        self.prompt_raw(u'|B|cff0000ff>> |r' + unicode(s) + u'\n')

    def prompt_raw(self, s):
        self.parent.events_box.append(s)

    def begin_select_player(self, disables=[]):
        #if self.selecting_player: return
        self.selecting_player = True
        #self.selected_players = []
        for p in self.game.players:
            port = self.player2portrait(p)

            if p in disables:
                port.disabled = True
                port.selected = False
                try:
                    self.selected_players.remove(p)
                except ValueError:
                    pass
            else:
                port.disabled = False

    def get_selected_players(self):
        return self.selected_players

    def set_selected_players(self, players):
        for p in self.char_portraits:
            p.selected = False

        for p in players:
            self.player2portrait(p).selected = True

        self.selected_players = players[:]

    def end_select_player(self):
        #if not self.selecting_player: return
        self.selecting_player = False
        self.selected_players = []
        for p in self.char_portraits:
            p.selected = False
            p.disabled = False

    def get_selected_cards(self):
        return [
            cs.associated_card
            for cs in self.handcard_area.cards if cs.hca_selected
        ] + [
            cs.associated_card
            for cs in self.player2portrait(self.game.me).equipcard_area.cards
            if cs.selected
        ]

    def get_selected_skills(self):
        skills = self.game.me.skills
        return sorted([skills[i] for i in self.skill_box.get_selected_index()],
                      key=lambda s: s.sort_index)

    def on_mouse_click(self, x, y, button, modifier):
        c = self.control_frompoint1_recursive(x, y)
        if isinstance(c, GameCharacterPortrait
                      ) and self.selecting_player and not c.disabled:
            char = c.character
            if not char: return True
            sel = c.selected
            psel = self.selected_players
            if sel:
                c.selected = False
                psel.remove(char)
            else:
                c.selected = True
                psel.append(char)
            self.dispatch_event('on_selection_change')
        return True

    def get_game_screen(self):
        assert self.parent
        return self.parent

    @staticmethod
    def show_result(g):
        ResultPanel(g, parent=Overlay.cur_overlay)
예제 #6
0
파일: view.py 프로젝트: npk/thbattle
class THBattleUI(Control):
    portrait_location = [
        (60, 300, Colors.blue),
        (250, 450, Colors.orange),
        (450, 450, Colors.blue),
        (640, 300, Colors.orange),
        (450, 150, Colors.blue),
        (250, 150, Colors.orange),
    ]

    gcp_location = [
        (3, 1, "me", Colors.blue),
        (669, 280, "left", Colors.orange),
        (155 + 180 + 180, 520, "bottom", Colors.blue),
        (155 + 180, 520, "bottom", Colors.orange),
        (155, 520, "bottom", Colors.blue),
        (3, 280, "right", Colors.orange),
    ]

    def __init__(self, game, *a, **k):
        self.game = game
        game.event_observer = UIEventHook

        Control.__init__(self, *a, **k)

        self.char_portraits = None

        self.deck_indicator = DeckIndicator(parent=self, x=30, y=680, width=50, height=25)

        self.handcard_area = HandCardArea(parent=self, x=238, y=9, zindex=3, width=93 * 5 + 42, height=145)

        self.deck_area = PortraitCardArea(
            parent=self, width=1, height=1, x=self.width // 2, y=self.height // 2, zindex=4
        )

        self.btn_afk = Button(parent=self, caption=u"让⑨帮你玩", zindex=1, color=Colors.blue, **r2d((730, 640, 75, 25)))

        self.gameintro_icon = GameIntroIcon(parent=self, game=game, **r2d((780, 610, 25, 25)))

        self.afk = False

        @self.btn_afk.event
        def on_click():
            v = not self.afk
            self.afk = v
            self.btn_afk.color = (Colors.blue, Colors.orange)[v]
            self.btn_afk.update()

        @self.handcard_area.event
        def on_selection_change():
            self.dispatch_event("on_selection_change")

        self.dropcard_area = DropCardArea(parent=self, x=0, y=324, zindex=3, width=820, height=125)

        class Animations(pyglet.graphics.Batch, Control):
            def __init__(self, **k):
                pyglet.graphics.Batch.__init__(self)
                Control.__init__(self, x=0, y=0, width=0, height=0, zindex=2, **k)

            def hit_test(self, x, y):
                return False

        self.animations = Animations(parent=self)
        self.selecting_player = 0

    def init(self):
        ports = self.char_portraits = [
            GameCharacterPortrait(parent=self, color=color, x=x, y=y, tag_placement=tp)
            for x, y, tp, color in self.gcp_location[: len(self.game.players)]
        ]

        pl = self.game.players
        shift = pl.index(self.game.me)
        for i, c in enumerate(ports):
            p = pl[(shift + i) % self.game.n_persons]
            c.player = p
            c.update()

        ports[0].equipcard_area.selectable = True  # it's TheChosenOne

        self.begin_select_player()
        self.end_select_player()
        self.skill_box = SkillSelectionBox(parent=self, x=161, y=9, width=70, height=22 * 6 - 4)

        SoundManager.switch_bgm(gres.bgm_game)

    def player2portrait(self, p):
        for port in self.char_portraits:
            if port.player == p:
                break
        else:
            raise ValueError(p)
        return port

    def update_skillbox(self):
        g = self.game
        skills = getattr(g.me, "skills", None)
        if skills is None:
            # before girl chosen
            return

        skills = [
            (i, s, s.ui_meta.clickable(g)) for i, s in enumerate(skills) if not getattr(s.ui_meta, "no_display", False)
        ]

        skills.sort(key=lambda i: -i[2])

        self.skill_box.set_skills((s.ui_meta.name, i, e) for i, s, e in skills)

    PORT_UPDATE_MESSAGES = {"evt_game_begin", "evt_kof_next_character"}

    def update_portraits(self):
        for port in self.char_portraits:
            port.update()

    def on_message(self, _type, *args):
        if _type == "evt_action_before" and isinstance(args[0], actions.PlayerTurn):
            self.current_turn = args[0].target

        elif _type == "player_change":
            for i, pd in enumerate(args[0]):
                p = self.game.players[i]
                port = self.player2portrait(p)
                port.dropped = pd["state"] in ("dropped", "fleed")
                port.fleed = pd["state"] == "fleed"
                port.update()

        elif _type in self.PORT_UPDATE_MESSAGES:
            self.update_portraits()

        elif _type == "evt_action_after":
            act = args[0]
            meta = getattr(act, "ui_meta", None)
            if meta and getattr(meta, "update_portrait", None):
                pl = set()
                if act.source:
                    pl.add(act.source)

                if hasattr(act, "target_list"):
                    pl.update(act.target_list)
                elif act.target:
                    pl.add(act.target)

                for p in pl:
                    self.player2portrait(p).update()

        if _type.startswith("evt_"):
            effects.handle_event(self, _type[4:], args[0])
            inputs.handle_event(self, _type[4:], args[0])

    def draw(self):
        self.draw_subcontrols()

    def ray(self, f, t):
        if f == t:
            return
        sp = self.player2portrait(f)
        dp = self.player2portrait(t)
        x0, y0 = sp.x + sp.width / 2, sp.y + sp.height / 2
        x1, y1 = dp.x + dp.width / 2, dp.y + dp.height / 2
        Ray(x0, y0, x1, y1, parent=self, zindex=10)

    def prompt(self, s):
        self.prompt_raw(u"|B|cff0000ff>> |r" + unicode(s) + u"\n")

    def prompt_raw(self, s):
        self.parent.events_box.append(s)

    def begin_select_player(self, disables=[]):
        # if self.selecting_player: return
        self.selecting_player = True
        # self.selected_players = []
        for p in self.game.players:
            port = self.player2portrait(p)

            if p in disables:
                port.disabled = True
                port.selected = False
                try:
                    self.selected_players.remove(p)
                except ValueError:
                    pass
            else:
                port.disabled = False

    def get_selected_players(self):
        return self.selected_players

    def set_selected_players(self, players):
        for p in self.char_portraits:
            p.selected = False

        for p in players:
            self.player2portrait(p).selected = True

        self.selected_players = players[:]

    def end_select_player(self):
        # if not self.selecting_player: return
        self.selecting_player = False
        self.selected_players = []
        for p in self.char_portraits:
            p.selected = False
            p.disabled = False

    def get_selected_cards(self):
        return [cs.associated_card for cs in self.handcard_area.cards if cs.hca_selected] + [
            cs.associated_card for cs in self.player2portrait(self.game.me).equipcard_area.cards if cs.selected
        ]

    def get_selected_skills(self):
        skills = self.game.me.skills
        return sorted([skills[i] for i in self.skill_box.get_selected_index()], key=lambda s: s.sort_index)

    def on_mouse_click(self, x, y, button, modifier):
        c = self.control_frompoint1_recursive(x, y)
        if isinstance(c, GameCharacterPortrait) and self.selecting_player and not c.disabled:
            sel = c.selected
            psel = self.selected_players
            if sel:
                c.selected = False
                psel.remove(c.player)
            else:
                c.selected = True
                psel.append(c.player)
            self.dispatch_event("on_selection_change")
        return True

    def get_game_screen(self):
        assert self.parent
        return self.parent

    @staticmethod
    def show_result(g):
        ResultPanel(g, parent=Overlay.cur_overlay)