Esempio n. 1
0
    async def read_match(self) -> Match:
        """Read an osu! match from the internal buffer."""
        m = Match()

        # ignore match id (i16) and inprogress (i8).
        self._buf = self._buf[3:]

        m.type = MatchTypes(await self.read_i8())
        m.mods = Mods(await self.read_i32())

        m.name = await self.read_string()
        m.passwd = await self.read_string()

        # TODO: don't do this, do it like everyone else..

        # ignore the map's name, we're going
        # to get all it's info from the md5.
        await self.read_string()

        map_id = await self.read_i32()
        map_md5 = await self.read_string()

        m.bmap = await Beatmap.from_md5(map_md5)
        if not m.bmap and map_id != (1 << 32) - 1:
            # if they pick an unsubmitted map,
            # just give them vivid [insane] lol.
            vivid_md5 = '1cf5b2c2edfafd055536d2cefcb89c0e'
            m.bmap = await Beatmap.from_md5(vivid_md5)

        for slot in m.slots:
            slot.status = await self.read_i8()

        for slot in m.slots:
            slot.team = Teams(await self.read_i8())

        for slot in m.slots:
            if slot.status & SlotStatus.has_player:
                # we don't need this, ignore it.
                self._buf = self._buf[4:]

        host_id = await self.read_i32()
        m.host = await glob.players.get_by_id(host_id)

        m.mode = GameMode(await self.read_i8())
        m.match_scoring = MatchScoringTypes(await self.read_i8())
        m.team_type = MatchTeamTypes(await self.read_i8())
        m.freemods = await self.read_i8() == 1

        # if we're in freemods mode,
        # read individual slot mods.
        if m.freemods:
            for slot in m.slots:
                slot.mods = Mods(await self.read_i32())

        # read the seed (used for mania)
        m.seed = await self.read_i32()

        return m
Esempio n. 2
0
    async def read_match(self) -> Match:
        """Read an osu! match from the internal buffer."""
        m = Match()

        # ignore match id (i16) and inprogress (i8).
        self._buf = self._buf[3:]

        #m.type = MatchTypes(await self.read_i8())
        if await self.read_i8() == 1:
            point_of_interest() # what is powerplay

        m.mods = Mods(await self.read_i32())

        m.name = await self.read_string()
        m.passwd = await self.read_string()

        m.map_name = await self.read_string()
        m.map_id = await self.read_i32()
        m.map_md5 = await self.read_string()

        for slot in m.slots:
            slot.status = await self.read_i8()

        for slot in m.slots:
            slot.team = MatchTeams(await self.read_i8())

        for slot in m.slots:
            if slot.status & SlotStatus.has_player:
                # we don't need this, ignore it.
                self._buf = self._buf[4:]

        host_id = await self.read_i32()
        m.host = await glob.players.get(id=host_id)

        m.mode = GameMode(await self.read_i8())
        m.win_condition = MatchWinConditions(await self.read_i8())
        m.team_type = MatchTeamTypes(await self.read_i8())
        m.freemods = await self.read_i8() == 1

        # if we're in freemods mode,
        # read individual slot mods.
        if m.freemods:
            for slot in m.slots:
                slot.mods = Mods(await self.read_i32())

        # read the seed (used for mania)
        m.seed = await self.read_i32()

        return m
Esempio n. 3
0
async def mods(p: Player, c: Messageable, msg: Sequence[str]) -> str:
    if isinstance(c, Channel) or c.id != 1:
        return 'This command can only be used in DM with Aika.'

    if not p.last_np:
        return 'Please /np a map first!'

    msg = ''.join(msg).replace(' ', '')
    if msg[0] == '+': # remove +
        msg = msg[1:]

    mods = Mods.from_str(msg)

    if mods not in p.last_np.pp_cache:
        # cach
        await p.last_np.cache_pp(mods)

    # Since this is a DM to the bot, we should
    # send back a list of general PP values.
    # TODO: !acc and !mods in commands to
    #       modify these values :P
    _msg = [p.last_np.embed]
    if mods:
        _msg.append(f'{mods!r}')

    msg = f"{' '.join(_msg)}: " + ' | '.join(
        f'{acc}%: {pp:.2f}pp'
        for acc, pp in zip(
            (90, 95, 98, 99, 100),
            p.last_np.pp_cache[mods]
        ))

    return msg
Esempio n. 4
0
    async def maps_from_sql(self) -> None:
        """Retrieve all maps from sql to populate `self.maps`."""
        query = ('SELECT map_id, mods, slot '
                 'FROM tourney_pool_maps '
                 'WHERE pool_id = %s')

        for row in await glob.db.fetchall(query, [self.id]):
            map_id = row['map_id']
            bmap = await Beatmap.from_bid(map_id)

            if not bmap:
                # map not found? remove it from the
                # pool and log this incident to console.
                # NOTE: it's intentional that this removes
                # it from not only this pool, but all pools.
                # TODO: perhaps discord webhook?
                log(f'Removing {map_id} from pool {self.name} (not found).',
                    Ansi.LRED)

                await glob.db.execute(
                    'DELETE FROM tourney_pool_maps '
                    'WHERE map_id = %s', [map_id])
                continue

            key = (Mods(row['mods']), row['slot'])
            self.maps[key] = bmap
Esempio n. 5
0
    async def maps_from_sql(self, db_cursor: aiomysql.DictCursor) -> None:
        """Retrieve all maps from sql to populate `self.maps`."""
        await db_cursor.execute(
            "SELECT map_id, mods, slot FROM tourney_pool_maps WHERE pool_id = %s",
            [self.id],
        )

        async for row in db_cursor:
            map_id = row["map_id"]
            bmap = await Beatmap.from_bid(map_id)

            if not bmap:
                # map not found? remove it from the
                # pool and log this incident to console.
                # NOTE: it's intentional that this removes
                # it from not only this pool, but all pools.
                # TODO: perhaps discord webhook?
                log(f"Removing {map_id} from pool {self.name} (not found).",
                    Ansi.LRED)

                await db_cursor.execute(
                    "DELETE FROM tourney_pool_maps WHERE map_id = %s",
                    [map_id],
                )
                continue

            key: tuple[Mods, int] = (Mods(row["mods"]), row["slot"])
            self.maps[key] = bmap
Esempio n. 6
0
    def read_match(self) -> Match:
        """Read an osu! match from the internal buffer."""
        m = Match()

        # ignore match id (i16) and inprogress (i8).
        self.body_view = self.body_view[3:]

        self.read_i8()  # powerplay unused

        m.mods = Mods(self.read_i32())

        m.name = self.read_string()
        m.passwd = self.read_string()

        m.map_name = self.read_string()
        m.map_id = self.read_i32()
        m.map_md5 = self.read_string()

        for slot in m.slots:
            slot.status = SlotStatus(self.read_i8())

        for slot in m.slots:
            slot.team = MatchTeams(self.read_i8())

        for slot in m.slots:
            if slot.status & SlotStatus.has_player:
                # we don't need this, ignore it.
                self.body_view = self.body_view[4:]

        host_id = self.read_i32()
        m.host = glob.players.get(id=host_id)

        m.mode = GameMode(self.read_i8())
        m.win_condition = MatchWinConditions(self.read_i8())
        m.team_type = MatchTeamTypes(self.read_i8())
        m.freemods = self.read_i8() == 1

        # if we're in freemods mode,
        # read individual slot mods.
        if m.freemods:
            for slot in m.slots:
                slot.mods = Mods(self.read_i32())

        # read the seed (used for mania)
        m.seed = self.read_i32()

        return m
Esempio n. 7
0
async def recalc(p: 'Player', c: Messageable, msg: Sequence[str]) -> str:
    """Performs a full PP recalc on a specified map, or all maps."""
    if len(msg) != 1 or msg[0] not in ('map', 'all'):
        return 'Invalid syntax: !recalc <map/all>'

    score_counts = [] # keep track of # of scores recalced

    if msg[0] == 'map':
        # recalculate all scores on their last /np'ed map.
        if not p.last_np:
            return 'You must /np a map first!'

        ppcalc = await PPCalculator.from_id(p.last_np.id)
        if not ppcalc:
            return 'Could not retrieve map file.'

        await c.send(glob.bot, f'Performing full recalc on {p.last_np.embed}.')

        for table in ('scores_vn', 'scores_rx', 'scores_ap'):
            # fetch all scores from the table on this map
            scores = await glob.db.fetchall(
                'SELECT id, acc, mods, max_combo, '
                'n300, n100, n50, nmiss, ngeki, nkatu '
                f'FROM {table} WHERE map_md5 = %s '
                'AND status = 2 AND mode = 0',
                [p.last_np.md5]
            )

            score_counts.append(len(scores))

            if not scores:
                continue

            for score in scores:
                ppcalc.mods = Mods(score['mods'])
                ppcalc.combo = score['max_combo']
                ppcalc.nmiss = score['nmiss']
                ppcalc.acc = score['acc']

                pp, _ = await ppcalc.perform() # sr not needed

                await glob.db.execute(
                    f'UPDATE {table} '
                    'SET pp = %s '
                    'WHERE id = %s',
                    [pp, score['id']]
                )

    else:
        # recalculate all scores on every map
        if not p.priv & Privileges.Dangerous:
            return 'This command is limited to developers.'

        return 'TODO'

    recap = '{0} vn | {1} rx | {2} ap'.format(*score_counts)
    return f'Recalculated {sum(score_counts)} ({recap}) scores.'
Esempio n. 8
0
    async def from_sql(
        cls,
        sid: int,
        table: str,
        sort: str,
        t: int,
        ensure: bool = False,
    ) -> Optional["Score"]:
        score = await glob.db.fetchrow(f"SELECT * FROM {table} WHERE id = %s", [sid])

        if not score:
            return

        self = cls()

        self.id = sid

        self.map = await Beatmap.from_md5(score["md5"])

        if not self.map:
            return  # ?

        self.user = await glob.players.get(id=score["uid"], sql=ensure)

        if not self.user:
            return self

        self.pp = score["pp"]
        self.score = score["score"]
        self.combo = score["combo"]
        self.mods = Mods(score["mods"])
        self.acc = score["acc"]
        self.n300 = score["n300"]
        self.n100 = score["n100"]
        self.n50 = score["n50"]
        self.miss = score["miss"]
        self.geki = score["geki"]
        self.katu = score["katu"]
        self.grade = score["grade"]
        self.fc = score["fc"]
        self.status = scoreStatuses(score["status"])
        self.mode = lbModes(score["mode"], self.mods)

        self.time = score["time"]
        self.passed = self.status.value != 0

        if not self.user.restricted:
            self.rank = await self.calc_lb(table, sort, t)
        else:
            self.rank = 0

        self.osuver = score["osuver"]

        return self
Esempio n. 9
0
File: score.py Progetto: cmyui/Asahi
    async def from_sql(cls,
                       sid: int,
                       table: str,
                       sort: str,
                       t: int,
                       ensure: bool = False) -> Optional['Score']:
        score = await glob.db.fetchrow(f'SELECT * FROM {table} WHERE id = %s',
                                       [sid])

        if not score:
            return

        self = cls()

        self.id = sid

        self.map = await Beatmap.from_md5(score['md5'])

        if not self.map:
            return  # ?

        self.user = await glob.players.get(id=score['uid'], sql=ensure)

        if not self.user:
            return self

        self.pp = score['pp']
        self.score = score['score']
        self.combo = score['combo']
        self.mods = Mods(score['mods'])
        self.acc = score['acc']
        self.n300 = score['n300']
        self.n100 = score['n100']
        self.n50 = score['n50']
        self.miss = score['miss']
        self.geki = score['geki']
        self.katu = score['katu']
        self.grade = score['grade']
        self.fc = score['fc']
        self.status = scoreStatuses(score['status'])
        self.mode = lbModes(score['mode'].as_vn, self.mods)

        self.time = score['time']
        self.passed = self.status.value != 0

        if not self.user.restricted:
            self.rank = await self.calc_lb(table, sort, t)
        else:
            self.rank = 0

        self.osuver = score['osuver']

        return self
Esempio n. 10
0
    def update(self, action: int, info_text: str, map_md5: str, mods: int,
               mode: int, map_id: int) -> None:
        """Fully overwrite the class with new params."""

        # osu! sends both map id and md5, but
        # we'll only need one since we fetch a
        # beatmap obj from cache/sql anyways..
        self.action = Action(action)
        self.info_text = info_text
        self.map_md5 = map_md5
        self.mods = Mods(mods)
        self.mode = GameMode.from_params(mode, self.mods)
        self.map_id = map_id
Esempio n. 11
0
    async def maps_from_sql(self) -> None:
        """Retrieve all maps from sql to populate `self.maps`."""
        query = ('SELECT map_id, mods, slot '
                 'FROM tourney_pool_maps '
                 'WHERE pool_id = %s')

        async for row in glob.db.iterall(query, [self.id]):
            key = (Mods(row['mods']), row['slot'])
            bmap = await Beatmap.from_bid(row['map_id'])

            # TODO: should prolly delete the map from pool and
            # inform eventually webhook to disc if not found?
            self.maps[key] = bmap
Esempio n. 12
0
async def _with(p: Player, c: Messageable, msg: Sequence[str]) -> str:
    """Specify custom accuracy & mod combinations with `/np`."""
    if isinstance(c, Channel) or c.id != 1:
        return 'This command can only be used in DM with Aika.'

    if not p.last_np:
        return 'Please /np a map first!'

    # +?<mods> <acc>%?
    if 1 < len(msg) > 2:
        return 'Invalid syntax: !with <mods/acc> ...'

    mods = acc = None

    for param in (p.strip('+%') for p in msg):
        if cmyui._isdecimal(param, _float=True):
            acc = float(param)
        elif ~len(param) & 1: # len(param) % 2 == 0
            mods = Mods.from_str(param)
        else:
            return 'Invalid syntax: !with <mods/acc> ...'

    _msg = [p.last_np.embed]
    if not mods:
        mods = Mods.NOMOD

    _msg.append(repr(mods))

    if acc:
        # they're requesting pp for specified acc value.
        async with Owoppai(p.last_np.id, acc=acc, mods=mods) as owo:
            await owo.calc()
            pp_values = [(owo.acc, owo.pp)]
    else:
        # they're requesting pp for general accuracy values.
        if mods not in p.last_np.pp_cache:
            # cache
            await p.last_np.cache_pp(mods)

        pp_values = zip(
            (90, 95, 98, 99, 100),
            p.last_np.pp_cache[mods]
        )

    pp_msg = ' | '.join(f'{acc:.2f}%: {pp:.2f}pp'
                        for acc, pp in pp_values)
    return f"{' '.join(_msg)}: {pp_msg}"
Esempio n. 13
0
File: cho.py Progetto: Mxnuuel/gulag
    async def handle(self, p: Player) -> None:
        # update the user's status.
        p.status.action = Action(self.action)
        p.status.info_text = self.info_text
        p.status.map_md5 = self.map_md5
        p.status.mods = Mods(self.mods)

        if p.status.mods & Mods.RELAX:
            self.mode += 4
        elif p.status.mods & Mods.AUTOPILOT:
            self.mode = 7

        p.status.mode = GameMode(self.mode)
        p.status.map_id = self.map_id

        # broadcast it to all online players.
        glob.players.enqueue(packets.userStats(p))
Esempio n. 14
0
    async def from_submission(cls, data: list[str]) -> "Score":
        """Create a score object from an osu! submission string."""
        s = cls()
        """ parse the following format
        # 0  online_checksum
        # 1  n300
        # 2  n100
        # 3  n50
        # 4  ngeki
        # 5  nkatu
        # 6  nmiss
        # 7  score
        # 8  max_combo
        # 9  perfect
        # 10 grade
        # 11 mods
        # 12 passed
        # 13 gamemode
        # 14 play_time # yyMMddHHmmss
        # 15 osu_version + (" " * client_flags)
        """

        s.online_checksum = data[0]

        s.n300, s.n100, s.n50, s.ngeki, s.nkatu, s.nmiss, s.score, s.max_combo = map(
            int,
            data[1:9],
        )

        s.perfect = data[9] == "True"
        _grade = data[10]  # letter grade
        s.mods = Mods(int(data[11]))
        s.passed = data[12] == "True"
        s.mode = GameMode.from_params(int(data[13]), s.mods)

        # TODO: we might want to use data[14] to get more
        #       accurate submission time (client side) but
        #       we'd probably want to check if it's close.
        s.play_time = datetime.now()

        s.client_flags = ClientFlags(data[15].count(" ") & ~4)

        s.grade = Grade.from_str(_grade) if s.passed else Grade.F

        return s
Esempio n. 15
0
async def mp_mods(p: Player, m: Match, msg: Sequence[str]) -> str:
    """Set `m`'s mods, from string form."""
    if len(msg) != 1 or not ~len(msg[0]) & 1: # len(msg[0]) % 2 == 0
        return 'Invalid syntax: !mp mods <mods>'

    mods = Mods.from_str(msg[0])

    if m.freemods:
        if p.id == m.host.id:
            # allow host to set speed-changing mods.
            m.mods = mods & Mods.SPEED_CHANGING

        # set slot mods
        m.get_slot(p).mods = mods & ~Mods.SPEED_CHANGING
    else:
        # not freemods, set match mods.
        m.mods = mods

    m.enqueue(packets.updateMatch(m))
    return 'Match mods updated.'
Esempio n. 16
0
async def mp_mods(p: 'Player', m: 'Match', msg: Sequence[str]) -> str:
    """Set the current match's mods, from string form."""
    if len(msg) != 1 or not ~len(msg[0]) & 1:
        return 'Invalid syntax: !mp mods <mods>'

    mods = Mods.from_str(msg[0])

    if m.freemods:
        if p is m.host:
            # allow host to set speed-changing mods.
            m.mods = mods & Mods.SPEED_CHANGING

        # set slot mods
        m.get_slot(p).mods = mods & ~Mods.SPEED_CHANGING
    else:
        # not freemods, set match mods.
        m.mods = mods

    m.enqueue_state()
    return 'Match mods updated.'
Esempio n. 17
0
async def _with(p: 'Player', c: Messageable, msg: Sequence[str]) -> str:
    """Specify custom accuracy & mod combinations with `/np`."""
    if c is not glob.bot:
        return 'This command can only be used in DM with Aika.'

    if not p.last_np:
        return 'Please /np a map first!'

    # +?<mods> <acc>%?
    if 1 < len(msg) > 2:
        return 'Invalid syntax: !with <mods/acc> ...'

    mods = acc = None

    for param in (p.strip('+%') for p in msg):
        if cmyui._isdecimal(param, _float=True):
            if not 0 <= (acc := float(param)) <= 100:
                return 'Invalid accuracy.'

        elif ~len(param) & 1:  # len(param) % 2 == 0
            mods = Mods.from_str(param)
Esempio n. 18
0
    async def from_sql(cls, scoreid: int,
                       scores_table: str) -> Optional['Score']:
        """Create a score object from sql using it's scoreid."""
        # XXX: perhaps in the future this should take a gamemode rather
        # than just the sql table? just faster on the current setup :P
        res = await glob.db.fetch(
            'SELECT id, map_md5, userid, pp, score, '
            'max_combo, mods, acc, n300, n100, n50, '
            'nmiss, ngeki, nkatu, grade, perfect, '
            'status, mode, play_time, '
            'time_elapsed, client_flags, online_checksum '
            f'FROM {scores_table} WHERE id = %s', [scoreid],
            _dict=False)

        if not res:
            return

        s = cls()

        s.id = res[0]
        s.bmap = await Beatmap.from_md5(res[1])
        s.player = await glob.players.get_ensure(id=res[2])

        (s.pp, s.score, s.max_combo, s.mods, s.acc, s.n300, s.n100, s.n50,
         s.nmiss, s.ngeki, s.nkatu, s.grade, s.perfect, s.status, mode_vn,
         s.play_time, s.time_elapsed, s.client_flags,
         s.online_checksum) = res[3:]

        # fix some types
        s.passed = s.status != 0
        s.status = SubmissionStatus(s.status)
        s.grade = Grade.from_str(s.grade)
        s.mods = Mods(s.mods)
        s.mode = GameMode.from_params(mode_vn, s.mods)
        s.client_flags = ClientFlags(s.client_flags)

        if s.bmap:
            s.rank = await s.calc_lb_placement()

        return s
Esempio n. 19
0
File: cho.py Progetto: Mxnuuel/gulag
class SendPrivateMessage(BanchoPacket, type=Packets.OSU_SEND_PRIVATE_MESSAGE):
    msg: osuTypes.message

    async def handle(self, p: Player) -> None:
        if p.silenced:
            log(f'{p} tried to send a dm while silenced.', Ansi.YELLOW)
            return

        msg = self.msg.msg
        target = self.msg.target

        # allow this to get from sql - players can receive
        # messages offline, due to the mail system. B)
        if not (t := await glob.players.get(name=target, sql=True)):
            log(f'{p} tried to write to non-existent user {target}.',
                Ansi.YELLOW)
            return

        if t.pm_private and p.id not in t.friends:
            p.enqueue(packets.userDMBlocked(target))
            log(f'{p} tried to message {t}, but they are blocking dms.')
            return

        if t.silenced:
            # if target is silenced, inform player.
            p.enqueue(packets.targetSilenced(target))
            log(f'{p} tried to message {t}, but they are silenced.')
            return

        msg = f'{msg[:2045]}...' if msg[2048:] else msg

        if t.status.action == Action.Afk and t.away_msg:
            # send away message if target is afk and has one set.
            await p.send(t, t.away_msg)

        if t is glob.bot:
            # may have a command in the message.
            cmd = (msg.startswith(glob.config.command_prefix)
                   and await commands.process_commands(p, t, msg))

            if cmd:
                # command triggered, send response if any.
                if 'resp' in cmd:
                    await p.send(t, cmd['resp'])
            else:
                # no commands triggered.
                if match := regexes.now_playing.match(msg):
                    # user is /np'ing a map.
                    # save it to their player instance
                    # so we can use this elsewhere owo..
                    p.last_np = await Beatmap.from_bid(int(match['bid']))

                    if p.last_np:
                        if match['mods']:
                            # [1:] to remove leading whitespace
                            mods = Mods.from_np(match['mods'][1:])
                        else:
                            mods = Mods.NOMOD

                        if mods not in p.last_np.pp_cache:
                            await p.last_np.cache_pp(mods)

                        # since this is a DM to the bot, we should
                        # send back a list of general PP values.
                        # TODO: !acc and !mods in commands to
                        #       modify these values :P
                        _msg = [p.last_np.embed]
                        if mods:
                            _msg.append(f'+{mods!r}')

                        msg = f"{' '.join(_msg)}: " + ' | '.join([
                            f'{acc}%: {pp:.2f}pp'
                            for acc, pp in zip((90, 95, 98, 99,
                                                100), p.last_np.pp_cache[mods])
                        ])

                    else:
                        msg = 'Could not find map.'

                    await p.send(t, msg)
Esempio n. 20
0
class SendPrivateMessage(BanchoPacket, type=Packets.OSU_SEND_PRIVATE_MESSAGE):
    msg: osuTypes.message

    async def handle(self, p: Player) -> None:
        if p.silenced:
            log(f'{p} tried to send a dm while silenced.', Ansi.LYELLOW)
            return

        # remove leading/trailing whitespace
        msg = self.msg.msg.strip()
        t_name = self.msg.target

        # allow this to get from sql - players can receive
        # messages offline, due to the mail system. B)
        if not (t := await glob.players.get_ensure(name=t_name)):
            log(f'{p} tried to write to non-existent user {t_name}.',
                Ansi.LYELLOW)
            return

        if t.pm_private and p.id not in t.friends:
            p.enqueue(packets.userDMBlocked(t_name))
            log(f'{p} tried to message {t}, but they are blocking dms.')
            return

        if t.silenced:
            # if target is silenced, inform player.
            p.enqueue(packets.targetSilenced(t_name))
            log(f'{p} tried to message {t}, but they are silenced.')
            return

        # limit message length to 2k chars
        # perhaps this could be dangerous with !py..?
        if len(msg) > 2000:
            msg = f'{msg[:2000]}... (truncated)'
            p.enqueue(
                packets.notification('Your message was truncated\n'
                                     '(exceeded 2000 characters).'))

        if t.status.action == Action.Afk and t.away_msg:
            # send away message if target is afk and has one set.
            p.send(t.away_msg, sender=t)

        if t is glob.bot:
            # may have a command in the message.
            cmd = (msg.startswith(glob.config.command_prefix)
                   and await commands.process_commands(p, t, msg))

            if cmd:
                # command triggered, send response if any.
                if 'resp' in cmd:
                    p.send(cmd['resp'], sender=t)
            else:
                # no commands triggered.
                if match := regexes.now_playing.match(msg):
                    # user is /np'ing a map.
                    # save it to their player instance
                    # so we can use this elsewhere owo..
                    bmap = await Beatmap.from_bid(int(match['bid']))

                    if bmap:
                        # parse mode_vn int from regex
                        if match['mode_vn'] is not None:
                            mode_vn = {
                                'Taiko': 1,
                                'CatchTheBeat': 2,
                                'osu!mania': 3
                            }[match['mode_vn']]
                        else:
                            # use beatmap mode if not specified
                            mode_vn = bmap.mode.as_vanilla

                        p.last_np = {
                            'bmap': bmap,
                            'mode_vn': mode_vn,
                            'timeout': time.time() + 300  # 5mins
                        }

                        # calc pp if possible
                        if not glob.oppai_built:
                            msg = 'No oppai-ng binary was found at startup.'
                        elif mode_vn not in (0, 1):
                            msg = 'PP not yet supported for that mode.'
                        else:
                            if match['mods'] is not None:
                                # [1:] to remove leading whitespace
                                mods = Mods.from_np(match['mods'][1:], mode_vn)
                            else:
                                mods = Mods.NOMOD

                            if mods not in bmap.pp_cache:
                                await bmap.cache_pp(mods)

                            # since this is a DM to the bot, we should
                            # send back a list of general PP values.
                            _msg = [bmap.embed]
                            if mods:
                                _msg.append(f'+{mods!r}')

                            msg = f"{' '.join(_msg)}: " + ' | '.join([
                                f'{acc}%: {pp:.2f}pp'
                                for acc, pp in zip((90, 95, 98, 99,
                                                    100), bmap.pp_cache[mods])
                            ])

                    else:
                        msg = 'Could not find map.'

                        # time out their previous /np
                        p.last_np['timeout'] = 0

                    p.send(msg, sender=t)
Esempio n. 21
0
File: score.py Progetto: cmyui/Asahi
class Score:
    __slots__ = ('id', 'map', 'user', 'score', 'acc', 'n300', 'n100', 'n50',
                 'miss', 'geki', 'katu', 'grade', 'mods', 'readable_mods',
                 'combo', 'mode', 'rank', 'pp', 'sr', 'fc', 'passed', 'status',
                 'time', 'old_best', 'osuver', 'ur')

    def __init__(self) -> None:
        self.id: Optional[int] = None
        self.map: Optional[Beatmap] = None
        self.user: Optional[Player] = None

        self.score: Optional[int] = None
        self.acc: Optional[float] = None
        self.n300: Optional[int] = None
        self.n100: Optional[int] = None
        self.n50: Optional[int] = None
        self.miss: Optional[int] = None
        self.geki: Optional[int] = None
        self.katu: Optional[int] = None
        self.grade: Optional[Grade] = None
        self.mods: Optional[Mods] = None
        self.readable_mods: Optional[str] = None
        self.combo: Optional[int] = None
        self.mode: Optional[osuModes] = None

        self.rank: Optional[int] = None
        self.pp: Optional[float] = None
        self.sr: Optional[float] = None

        self.fc: Optional[bool] = None
        self.passed: Optional[bool] = None
        self.status: Optional[scoreStatuses] = None
        self.time: Optional[int] = None

        self.old_best: Optional[Score] = None

        self.osuver: Optional[int] = None
        self.ur: Optional[float] = None

    async def format(self) -> str:
        msg = (
            f'{self.user.name} | {self.map.name} +{self.readable_mods} {self.acc:.2f}% '
            f'{"FC" if not self.miss else f"{self.miss}xMiss"} {self.pp:,.0f}pp'
        )

        if self.miss:
            fc_score = copy.copy(self)

            fc_score.fc = True
            fc_score.combo = 0  # oppai will take max combo
            pp, _ = await fc_score.calc_pp(self.mode.as_vn)

            msg += f' (~{round(pp):,}pp for FC)'

        if self.mode.value == 0 and self.ur:
            msg += f' | {self.ur:.2f} (cv)UR'

        return msg

    @classmethod
    async def from_sql(cls,
                       sid: int,
                       table: str,
                       sort: str,
                       t: int,
                       ensure: bool = False) -> Optional['Score']:
        score = await glob.db.fetchrow(f'SELECT * FROM {table} WHERE id = %s',
                                       [sid])

        if not score:
            return

        self = cls()

        self.id = sid

        self.map = await Beatmap.from_md5(score['md5'])

        if not self.map:
            return  # ?

        self.user = await glob.players.get(id=score['uid'], sql=ensure)

        if not self.user:
            return self

        self.pp = score['pp']
        self.score = score['score']
        self.combo = score['combo']
        self.mods = Mods(score['mods'])
        self.acc = score['acc']
        self.n300 = score['n300']
        self.n100 = score['n100']
        self.n50 = score['n50']
        self.miss = score['miss']
        self.geki = score['geki']
        self.katu = score['katu']
        self.grade = score['grade']
        self.fc = score['fc']
        self.status = scoreStatuses(score['status'])
        self.mode = lbModes(score['mode'].as_vn, self.mods)

        self.time = score['time']
        self.passed = self.status.value != 0

        if not self.user.restricted:
            self.rank = await self.calc_lb(table, sort, t)
        else:
            self.rank = 0

        self.osuver = score['osuver']

        return self

    @classmethod
    async def from_submission(cls, base: str, iv: str, pw: str,
                              ver: str) -> Optional['Score']:
        rijndael = RijndaelCbc(  # much better f**k one liners
            key=f'osu!-scoreburgr---------{ver}',
            iv=b64decode(iv),
            padding=Pkcs7Padding(32),
            block_size=32)

        data = rijndael.decrypt(b64decode(base)).decode().split(':')

        self = cls()

        self.map = await Beatmap.from_md5(data[0])

        if (u := await glob.players.get(name=data[1].rstrip())) and u.pw == pw:
            self.user = u

        if not self.user:
            return self  # even if user isnt found, may be related to connection and we want to tell the client to retry

        if not self.map:
            return  # ??

        # i wanted to make everything be set in the same order as init but some require all score info to exist first so sadly not :c
        self.score = int(data[9])
        self.n300 = int(data[3])
        self.n100 = int(data[4])
        self.n50 = int(data[5])
        self.miss = int(data[8])
        self.geki = int(data[6])
        self.katu = int(data[7])
        self.mods = Mods(int(data[13]))
        self.readable_mods = repr(Mods(int(data[13])))
        self.combo = int(data[10])
        self.mode = lbModes(int(data[15]), self.mods)

        self.fc = data[11] == 'True'  # WHY IS OSU GIVING STRING FOR BOOL!!!!!!
        self.passed = data[14] == 'True'  # AGAIN OSU WHY!!!!
        self.time = round(
            time.time())  # have to add round cast cus it gives float smh

        self.grade = data[12] if self.passed else 'F'

        await self.calc_info()
        self.pp, self.sr = await self.calc_pp(self.mode.as_vn)
        await self.score_order()

        if self.user.restricted:
            self.rank = 0

        self.osuver = float(re.sub("[^0-9]", "", ver))  # lol

        return self
Esempio n. 22
0
            and await commands.process_commands(p, t, msg)

        if cmd and 'resp' in cmd:
            # Command triggered and there is a response to send.
            p.enqueue(await packets.sendMessage(t.name, cmd['resp'], client,
                                                t.id))
        else:  # No command triggered.
            if match := regexes.now_playing.match(msg):
                # User is /np'ing a map.
                # Save it to their player instance
                # so we can use this elsewhere owo..
                p.last_np = await Beatmap.from_bid(int(match['bid']))
                if p.last_np:
                    if match['mods']:
                        # [1:] to remove leading whitespace
                        mods = Mods.from_np(match['mods'][1:])
                    else:
                        mods = Mods.NOMOD

                    if mods not in p.last_np.pp_cache:
                        await p.last_np.cache_pp(mods)

                    # Since this is a DM to the bot, we should
                    # send back a list of general PP values.
                    # TODO: !acc and !mods in commands to
                    #       modify these values :P
                    _msg = [p.last_np.embed]
                    if mods:
                        _msg.append(f'{mods!r}')

                    msg = f"{' '.join(_msg)}: " + ' | '.join(
Esempio n. 23
0
class Score:
    __slots__ = (
        "id",
        "map",
        "user",
        "score",
        "acc",
        "n300",
        "n100",
        "n50",
        "miss",
        "geki",
        "katu",
        "grade",
        "mods",
        "readable_mods",
        "combo",
        "mode",
        "rank",
        "pp",
        "sr",
        "fc",
        "passed",
        "status",
        "time",
        "old_best",
        "osuver",
        "ur",
    )

    def __init__(self) -> None:
        self.id: Optional[int] = None
        self.map: Optional[Beatmap] = None
        self.user: Optional[Player] = None

        self.score: Optional[int] = None
        self.acc: Optional[float] = None
        self.n300: Optional[int] = None
        self.n100: Optional[int] = None
        self.n50: Optional[int] = None
        self.miss: Optional[int] = None
        self.geki: Optional[int] = None
        self.katu: Optional[int] = None
        self.grade: Optional[Grade] = None
        self.mods: Optional[Mods] = None
        self.readable_mods: Optional[str] = None
        self.combo: Optional[int] = None
        self.mode: Optional[osuModes] = None

        self.rank: Optional[int] = None
        self.pp: Optional[float] = None
        self.sr: Optional[float] = None

        self.fc: Optional[bool] = None
        self.passed: Optional[bool] = None
        self.status: Optional[scoreStatuses] = None
        self.time: Optional[int] = None

        self.old_best: Optional[Score] = None

        self.osuver: Optional[float] = None
        self.ur: Optional[float] = None

    async def format(self) -> str:
        msg = (
            f"{self.user.name} | {self.map.name} +{self.readable_mods} {self.acc:.2f}% "
            f'{"FC" if not self.miss else f"{self.miss}xMiss"} {self.pp:,.0f}pp'
        )

        if self.miss:
            fc_score = copy.copy(self)

            fc_score.fc = True
            fc_score.combo = 0  # oppai will take max combo
            pp, _ = await fc_score.calc_pp(self.mode.as_vn)

            msg += f" (~{round(pp):,}pp for FC)"

        if self.mode.value == 0 and self.ur:
            msg += f" | {self.ur:.2f} (cv)UR"

        return msg

    @classmethod
    async def from_sql(
        cls,
        sid: int,
        table: str,
        sort: str,
        t: int,
        ensure: bool = False,
    ) -> Optional["Score"]:
        score = await glob.db.fetchrow(f"SELECT * FROM {table} WHERE id = %s", [sid])

        if not score:
            return

        self = cls()

        self.id = sid

        self.map = await Beatmap.from_md5(score["md5"])

        if not self.map:
            return  # ?

        self.user = await glob.players.get(id=score["uid"], sql=ensure)

        if not self.user:
            return self

        self.pp = score["pp"]
        self.score = score["score"]
        self.combo = score["combo"]
        self.mods = Mods(score["mods"])
        self.acc = score["acc"]
        self.n300 = score["n300"]
        self.n100 = score["n100"]
        self.n50 = score["n50"]
        self.miss = score["miss"]
        self.geki = score["geki"]
        self.katu = score["katu"]
        self.grade = score["grade"]
        self.fc = score["fc"]
        self.status = scoreStatuses(score["status"])
        self.mode = lbModes(score["mode"], self.mods)

        self.time = score["time"]
        self.passed = self.status.value != 0

        if not self.user.restricted:
            self.rank = await self.calc_lb(table, sort, t)
        else:
            self.rank = 0

        self.osuver = score["osuver"]

        return self

    @classmethod
    async def from_submission(
        cls,
        base: str,
        iv: str,
        pw: str,
        ver: str,
    ) -> Optional["Score"]:
        rijndael = RijndaelCbc(  # much better f**k one liners
            key=f"osu!-scoreburgr---------{ver}".encode(),
            iv=b64decode(iv),
            padding=Pkcs7Padding(32),
            block_size=32,
        )

        data = rijndael.decrypt(b64decode(base)).decode().split(":")

        self = cls()

        self.map = await Beatmap.from_md5(data[0])

        if (u := await glob.players.get(name=data[1].rstrip())) and u.pw == pw:
            self.user = u

        if not self.user:
            return self  # even if user isnt found, may be related to connection and we want to tell the client to retry

        if not self.map:
            return  # ??

        # i wanted to make everything be set in the same order as init but some require all score info to exist first so sadly not :c
        self.score = int(data[9])
        self.n300 = int(data[3])
        self.n100 = int(data[4])
        self.n50 = int(data[5])
        self.miss = int(data[8])
        self.geki = int(data[6])
        self.katu = int(data[7])
        self.mods = Mods(int(data[13]))
        self.readable_mods = repr(Mods(int(data[13])))
        self.combo = int(data[10])
        self.mode = lbModes(int(data[15]), self.mods)

        self.fc = data[11] == "True"  # WHY IS OSU GIVING STRING FOR BOOL!!!!!!
        self.passed = data[14] == "True"  # AGAIN OSU WHY!!!!
        self.time = round(time.time())  # have to add round cast cus it gives float smh

        self.grade = data[12] if self.passed else "F"

        await self.calc_info()
        self.pp, self.sr = await self.calc_pp(self.mode.as_vn)
        await self.score_order()

        if self.user.restricted:
            self.rank = 0

        self.osuver = float(re.sub("[^0-9]", "", ver))  # lol

        return self
Esempio n. 24
0
class SendPrivateMessage(ClientPacket,
                         type=ClientPacketType.SEND_PRIVATE_MESSAGE):
    msg = osuTypes.message

    async def handle(self, p: Player) -> None:
        if p.silenced:
            log(f'{p} tried to send a dm while silenced.', Ansi.YELLOW)
            return

        msg = self.msg.msg
        target = self.msg.target

        if not (t := await glob.players.get_by_name(target)):
            log(f'{p} tried to write to non-existant user {target}.',
                Ansi.YELLOW)
            return

        if t.pm_private and p.id not in t.friends:
            p.enqueue(packets.userPMBlocked(target))
            log(f'{p} tried to message {t}, but they are blocking dms.')
            return

        if t.silenced:
            p.enqueue(packets.targetSilenced(target))
            log(f'{p} tried to message {t}, but they are silenced.')
            return

        msg = f'{msg[:2045]}...' if msg[2048:] else msg
        client, client_id = p.name, p.id

        if t.status.action == Action.Afk and t.away_msg:
            # send away message if target is afk and has one set.
            p.enqueue(
                packets.sendMessage(client, t.away_msg, target, client_id))

        if t.id == 1:
            # target is the bot, check if message is a command.
            cmd = msg.startswith(glob.config.command_prefix) \
            and await commands.process_commands(p, t, msg)

            if cmd and 'resp' in cmd:
                # command triggered and there is a response to send.
                p.enqueue(
                    packets.sendMessage(t.name, cmd['resp'], client, t.id))

            else:
                # no commands triggered.
                if match := regexes.now_playing.match(msg):
                    # user is /np'ing a map.
                    # save it to their player instance
                    # so we can use this elsewhere owo..
                    p.last_np = await Beatmap.from_bid(int(match['bid']))

                    if p.last_np:
                        if match['mods']:
                            # [1:] to remove leading whitespace
                            mods = Mods.from_np(match['mods'][1:])
                        else:
                            mods = Mods.NOMOD

                        if mods not in p.last_np.pp_cache:
                            await p.last_np.cache_pp(mods)

                        # since this is a DM to the bot, we should
                        # send back a list of general PP values.
                        # TODO: !acc and !mods in commands to
                        #       modify these values :P
                        _msg = [p.last_np.embed]
                        if mods:
                            _msg.append(f'{mods!r}')

                        msg = f"{' '.join(_msg)}: " + ' | '.join(
                            f'{acc}%: {pp:.2f}pp'
                            for acc, pp in zip((90, 95, 98, 99, 100
                                                ), p.last_np.pp_cache[mods]))

                    else:
                        msg = 'Could not find map.'

                    p.enqueue(packets.sendMessage(t.name, msg, client, t.id))
Esempio n. 25
0
class Score:
    """\
    Server side representation of an osu! score; any gamemode.

    Attributes
    -----------
    id: `int`
        The score's unique ID.

    bmap: Optional[`Beatmap`]
        A beatmap obj representing the osu map.

    player: Optional[`Player`]
        A player obj of the player who submitted the score.

    pp: `float`
        The score's performance points.

    score: `int`
        The score's osu! score value.

    max_combo: `int`
        The maximum combo reached in the score.

    mods: `Mods`
        A bitwise value of the osu! mods used in the score.

    acc: `float`
        The accuracy of the score.

    n300: `int`
        The number of 300s in the score.

    n100: `int`
        The number of 100s in the score (150s if taiko).

    n50: `int`
        The number of 50s in the score.

    nmiss: `int`
        The number of misses in the score.

    ngeki: `int`
        The number of gekis in the score.

    nkatu: `int`
        The number of katus in the score.

    grade: `str`
        The letter grade in the score.

    rank: `int`
        The leaderboard placement of the score.

    passed: `bool`
        Whether the score completed the map.

    perfect: `bool`
        Whether the score is a full-combo.

    status: `SubmissionStatus`
        The submission status of the score.

    mode: `GameMode`
        The game mode of the score.

    play_time: `datetime`
        A datetime obj of the time of score submission.

    time_elapsed: `int`
        The total elapsed time of the play (in milliseconds).

    client_flags: `int`
        osu!'s old anticheat flags.

    prev_best: Optional[`Score`]
        The previous best score before this play was submitted.
        NOTE: just because a score has a `prev_best` attribute does
        mean the score is our best score on the map! the `status`
        value will always be accurate for any score.
    """
    __slots__ = (
        'id', 'bmap', 'player',
        'pp', 'sr', 'score', 'max_combo', 'mods',
        'acc', 'n300', 'n100', 'n50', 'nmiss', 'ngeki', 'nkatu', 'grade',
        'rank', 'passed', 'perfect', 'status',
        'mode', 'play_time', 'time_elapsed',
        'client_flags', 'prev_best'
    )

    def __init__(self):
        self.id: Optional[int] = None

        self.bmap: Optional[Beatmap] = None
        self.player: Optional['Player'] = None

        # pp & star rating
        self.pp: Optional[float] = None
        self.sr: Optional[float] = None

        self.score: Optional[int] = None
        self.max_combo: Optional[int] = None
        self.mods: Optional[Mods] = None

        self.acc: Optional[float] = None
        # TODO: perhaps abstract these differently
        # since they're mode dependant? feels weird..
        self.n300: Optional[int] = None
        self.n100: Optional[int] = None # n150 for taiko
        self.n50: Optional[int] = None
        self.nmiss: Optional[int] = None
        self.ngeki: Optional[int] = None
        self.nkatu: Optional[int] = None
        self.grade: Optional[Rank] = None

        self.rank: Optional[int] = None
        self.passed: Optional[bool] = None
        self.perfect: Optional[bool] = None
        self.status: Optional[SubmissionStatus] = None

        self.mode: Optional[GameMode] = None
        self.play_time: Optional[datetime] = None
        self.time_elapsed: Optional[datetime] = None

        # osu!'s client 'anticheat'.
        self.client_flags: Optional[ClientFlags] = None

        self.prev_best: Optional[Score] = None

    @classmethod
    async def from_sql(cls, scoreid: int, sql_table: str):
        """Create a score object from sql using it's scoreid."""
        # XXX: perhaps in the future this should take a gamemode rather
        # than just the sql table? just faster on the current setup :P
        res = await glob.db.fetch(
            'SELECT id, map_md5, userid, pp, score, '
            'max_combo, mods, acc, n300, n100, n50, '
            'nmiss, ngeki, nkatu, grade, perfect, '
            'status, mode, play_time, '
            'time_elapsed, client_flags '
            f'FROM {sql_table} WHERE id = %s',
            [scoreid], _dict=False
        )

        if not res:
            return

        s = cls()

        s.id = res[0]
        s.bmap = await Beatmap.from_md5(res[1])
        s.player = await glob.players.get(id=res[2], sql=True)

        (s.pp, s.score, s.max_combo, s.mods, s.acc, s.n300,
         s.n100, s.n50, s.nmiss, s.ngeki, s.nkatu, s.grade,
         s.perfect, s.status, mode_vn, s.play_time,
         s.time_elapsed, s.client_flags) = res[3:]

        # fix some types
        s.passed = s.status != 0
        s.status = SubmissionStatus(s.status)
        s.mods = Mods(s.mods)
        s.mode = GameMode.from_params(mode_vn, s.mods)
        s.client_flags = ClientFlags(s.client_flags)

        if s.bmap:
            s.rank = await s.calc_lb_placement()

        return s

    @classmethod
    async def from_submission(cls, data_b64: str, iv_b64: str,
                              osu_ver: str, pw_md5: str) -> Optional['Score']:
        """Create a score object from an osu! submission string."""
        iv = b64decode(iv_b64).decode('latin_1')
        data_aes = b64decode(data_b64).decode('latin_1')

        aes_key = f'osu!-scoreburgr---------{osu_ver}'
        aes = RijndaelCbc(aes_key, iv, ZeroPadding(32), 32)

        # score data is delimited by colons (:).
        data = aes.decrypt(data_aes).decode().split(':')

        if len(data) != 18:
            log('Received an invalid score submission.', Ansi.LRED)
            return

        s = cls()

        if len(map_md5 := data[0]) != 32:
            return

        pname = data[1].rstrip() # why does osu! make me rstrip lol

        # get the map & player for the score.
        s.bmap = await Beatmap.from_md5(map_md5)
        s.player = await glob.players.get_login(pname, pw_md5)

        if not s.player:
            # return the obj with an empty player to
            # determine whether the score faield to
            # be parsed vs. the user could not be found
            # logged in (we want to not send a reply to
            # the osu! client if they're simply not logged
            # in, so that it will retry once they login).
            return s

        # XXX: unused idx 2: online score checksum
        # perhaps will use to improve security at some point?

        # ensure all ints are safe to cast.
        if not all(map(lambda x: x.isdecimal(), data[3:11] + [data[13], data[15]])):
            log('Invalid parameter passed into submit-modular.', Ansi.LRED)
            return

        (s.n300, s.n100, s.n50, s.ngeki, s.nkatu, s.nmiss,
         s.score, s.max_combo) = map(int, data[3:11])

        s.perfect = data[11] == 'True'
        _grade = data[12] # letter grade
        s.mods = Mods(int(data[13]))
        s.passed = data[14] == 'True'
        s.mode = GameMode.from_params(int(data[15]), s.mods)
        s.play_time = datetime.now()
        s.client_flags = data[17].count(' ') # TODO: use osu!ver? (osuver\s+)

        s.grade = _grade if s.passed else 'F'

        # all data read from submission.
        # now we can calculate things based on our data.
        s.calc_accuracy()

        if s.bmap:
            # ignore sr for now.
            s.pp, s.sr = await s.calc_diff()

            await s.calc_status()
            s.rank = await s.calc_lb_placement()
        else:
            s.pp = s.sr = 0.0
            s.status = SubmissionStatus.SUBMITTED if s.passed \
                  else SubmissionStatus.FAILED

        return s
Esempio n. 26
0
    async def from_submission(cls, data_b64: str, iv_b64: str, osu_ver: str,
                              pw_md5: str) -> Optional['Score']:
        """Create a score object from an osu! submission string."""
        aes = RijndaelCbc(key=f'osu!-scoreburgr---------{osu_ver}',
                          iv=b64decode(iv_b64),
                          padding=Pkcs7Padding(32),
                          block_size=32)

        # score data is delimited by colons (:).
        data = aes.decrypt(b64decode(data_b64)).decode().split(':')

        if len(data) != 18:
            log('Received an invalid score submission.', Ansi.LRED)
            return

        s = cls()

        if len(data[0]) != 32 or len(data[2]) != 32:
            return

        map_md5 = data[0]
        pname = data[1].rstrip()  # rstrip 1 space if client has supporter
        s.online_checksum = data[2]

        # get the map & player for the score.
        s.bmap = await Beatmap.from_md5(map_md5)
        s.player = await glob.players.get_login(pname, pw_md5)

        if not s.player:
            # return the obj with an empty player to
            # determine whether the score failed to
            # be parsed vs. the user could not be found
            # logged in (we want to not send a reply to
            # the osu! client if they're simply not logged
            # in, so that it will retry once they login).
            return s

        # XXX: unused idx 2: online score checksum
        # perhaps will use to improve security at some point?

        # ensure all ints are safe to cast.
        if not all(map(str.isdecimal, data[3:11] + [data[13], data[15]])):
            log('Invalid parameter passed into submit-modular.', Ansi.LRED)
            return

        (s.n300, s.n100, s.n50, s.ngeki, s.nkatu, s.nmiss, s.score,
         s.max_combo) = map(int, data[3:11])

        s.perfect = data[11] == 'True'
        _grade = data[12]  # letter grade
        s.mods = Mods(int(data[13]))
        s.passed = data[14] == 'True'
        s.mode = GameMode.from_params(int(data[15]), s.mods)

        s.play_time = datetime.now()  # TODO: use data[16]

        s.client_flags = ClientFlags(data[17].count(' ') & ~4)

        s.grade = Grade.from_str(_grade) if s.passed else Grade.F

        # all data read from submission.
        # now we can calculate things based on our data.
        s.calc_accuracy()

        if s.bmap:
            osu_file_path = BEATMAPS_PATH / f'{s.bmap.id}.osu'
            if await ensure_local_osu_file(osu_file_path, s.bmap.id,
                                           s.bmap.md5):
                s.pp, s.sr = s.calc_diff(osu_file_path)

                if s.passed:
                    await s.calc_status()

                    if s.bmap.status != RankedStatus.Pending:
                        s.rank = await s.calc_lb_placement()
                else:
                    s.status = SubmissionStatus.FAILED
        else:
            s.pp = s.sr = 0.0
            if s.passed:
                s.status = SubmissionStatus.SUBMITTED
            else:
                s.status = SubmissionStatus.FAILED

        return s
Esempio n. 27
0
@mp_commands.add(Privileges.Normal)
async def mp_ban(p: 'Player', m: 'Match', msg: Sequence[str]) -> str:
    """Ban a pick in the currently loaded mappool."""
    if len(msg) != 1:
        return 'Invalid syntax: !mp ban <pick>'

    if not m.pool:
        return 'No pool currently selected!'

    mods_slot = msg[0]

    # separate mods & slot
    if not (rgx := regexes.mappool_pick.fullmatch(mods_slot)):
        return 'Invalid pick syntax; correct example: "HD2".'

    mods = Mods.from_str(rgx[1])
    slot = int(rgx[2])

    if (mods, slot) not in m.pool.maps:
        return f'Found no {mods_slot} pick in the pool.'

    if (mods, slot) in m.bans:
        return 'That pick is already banned!'

    m.bans.add((mods, slot))
    return f'{mods_slot} banned.'


@mp_commands.add(Privileges.Normal)
async def mp_unban(p: 'Player', m: 'Match', msg: Sequence[str]) -> str:
    """Unban a pick in the currently loaded mappool."""
Esempio n. 28
0
async def read_match(data: memoryview) -> tuple[Match, int]:
    """ Read an osu! match from `data`. """
    m = Match()

    # Ignore match id (i32) & inprogress (i8).
    offset = 3

    # Read match type (no idea what this is tbh).
    m.type = MatchTypes(data[offset])
    offset += 1

    # Read match mods.
    m.mods = Mods.from_bytes(data[offset:offset+4], 'little')
    offset += 4

    # Read match name & password.
    m.name, offs = await read_string(data[offset:])
    offset += offs
    m.passwd, offs = await read_string(data[offset:])
    offset += offs

    # Ignore map's name.
    if data[offset] == 0x0b:
        offset += sum(await read_uleb128(data[offset + 1:]))
    offset += 1

    # Read beatmap information (id & md5).
    map_id = int.from_bytes(data[offset:offset+4], 'little')
    offset += 4

    map_md5, offs = await read_string(data[offset:])
    offset += offs

    # Get beatmap object for map selected.
    m.bmap = await Beatmap.from_md5(map_md5)
    if not m.bmap and map_id != (1 << 32) - 1:
        # If they pick an unsubmitted map,
        # just give them Vivid [Insane] lol.
        vivid_md5 = '1cf5b2c2edfafd055536d2cefcb89c0e'
        m.bmap = await Beatmap.from_md5(vivid_md5)

    # Read slot statuses.
    for s in m.slots:
        s.status = data[offset]
        offset += 1

    # Read slot teams.
    for s in m.slots:
        s.team = Teams(data[offset])
        offset += 1

    for s in m.slots:
        if s.status & SlotStatus.has_player:
            # Dont think we need this?
            offset += 4

    # Read match host.
    user_id = int.from_bytes(data[offset:offset+4], 'little')
    m.host = await glob.players.get_by_id(user_id)
    offset += 4

    # Read match mode, match scoring,
    # team type, and freemods.
    m.mode = GameMode(data[offset])
    offset += 1
    m.match_scoring = MatchScoringTypes(data[offset])
    offset += 1
    m.team_type = MatchTeamTypes(data[offset])
    offset += 1
    m.freemods = data[offset] == 1
    offset += 1

    # If we're in freemods mode,
    # read individual slot mods.
    if m.freemods:
        for s in m.slots:
            s.mods = Mods.from_bytes(data[offset:offset+4], 'little')
            offset += 4

    # Read the seed from multi.
    # XXX: Used for mania random mod.
    m.seed = int.from_bytes(data[offset:offset+4], 'little')
    return m, offset + 4