Archived
1
0
Fork 0
This repository has been archived on 2024-04-26. You can view files and clone it, but cannot push or open issues or pull requests.
akari-bot/modules/maimai/libraries/maimai_best_50.py
2023-06-26 18:24:19 +08:00

420 lines
18 KiB
Python

import ujson as json
import math
import os
from typing import Optional, Dict, List, Tuple
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from core.builtins import ErrorMessage
from core.utils.http import post_url
from .maimaidx_music import get_cover_len5_id, TotalList
total_list = TotalList()
scoreRank = 'D C B BB BBB A AA AAA S S+ SS SS+ SSS SSS+'.split(' ')
combo = ' FC FC+ AP AP+'.split(' ')
diffs = 'Basic Advanced Expert Master Re:Master'.split(' ')
class ChartInfo(object):
def __init__(self, idNum: str, diff: int, tp: str, achievement: float, ra: int, comboId: int, scoreId: int,
title: str, ds: float, lv: str):
self.idNum = idNum
self.diff = diff
self.tp = tp
self.achievement = achievement
self.ra = computeRa(ds, achievement)
self.comboId = comboId
self.scoreId = scoreId
self.title = title
self.ds = ds
self.lv = lv
def __str__(self):
return '%-50s' % f'{self.title} [{self.tp}]' + f'{self.ds}\t{diffs[self.diff]}\t{self.ra}'
def __eq__(self, other):
return self.ra == other.ra
def __lt__(self, other):
return self.ra < other.ra
@classmethod
async def from_json(cls, data):
rate = ['d', 'c', 'b', 'bb', 'bbb', 'a', 'aa', 'aaa', 's', 'sp', 'ss', 'ssp', 'sss', 'sssp']
ri = rate.index(data["rate"])
fc = ['', 'fc', 'fcp', 'ap', 'app']
fi = fc.index(data["fc"])
return cls(
idNum=(await total_list.get()).by_title(data["title"]).id,
title=data["title"],
diff=data["level_index"],
ra=data["ra"],
ds=data["ds"],
comboId=fi,
scoreId=ri,
lv=data["level"],
achievement=data["achievements"],
tp=data["type"]
)
class BestList(object):
def __init__(self, size: int):
self.data = []
self.size = size
def push(self, elem: ChartInfo):
if len(self.data) >= self.size and elem < self.data[-1]:
return
self.data.append(elem)
self.data.sort()
self.data.reverse()
while (len(self.data) > self.size):
del self.data[-1]
def pop(self):
del self.data[-1]
def __str__(self):
return '[\n\t' + ', \n\t'.join([str(ci) for ci in self.data]) + '\n]'
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
class DrawBest(object):
def __init__(self, sdBest: BestList, dxBest: BestList, userName: str):
self.sdBest = sdBest
self.dxBest = dxBest
self.userName = self._stringQ2B(userName)
self.sdRating = 0
self.dxRating = 0
for sd in sdBest:
self.sdRating += computeRa(sd.ds, sd.achievement)
for dx in dxBest:
self.dxRating += computeRa(dx.ds, dx.achievement)
self.playerRating = self.sdRating + self.dxRating
self.pic_dir = 'assets/maimai/static/mai/pic/'
self.cover_dir = 'assets/maimai/static/mai/cover/'
self.img = Image.open(self.pic_dir + 'UI_TTR_BG_Base_Plus.png').convert('RGBA')
self.ROWS_IMG = [2]
for i in range(6):
self.ROWS_IMG.append(116 + 96 * i)
self.COLOUMS_IMG = []
for i in range(8):
self.COLOUMS_IMG.append(2 + 138 * i)
for i in range(4):
self.COLOUMS_IMG.append(988 + 138 * i)
self.draw()
def _Q2B(self, uchar):
"""单个字符 全角转半角"""
inside_code = ord(uchar)
if inside_code == 0x3000:
inside_code = 0x0020
else:
inside_code -= 0xfee0
if inside_code < 0x0020 or inside_code > 0x7e: # 转完之后不是半角字符返回原来的字符
return uchar
return chr(inside_code)
def _stringQ2B(self, ustring):
"""把字符串全角转半角"""
return "".join([self._Q2B(uchar) for uchar in ustring])
def _getCharWidth(self, o) -> int:
widths = [
(126, 1), (159, 0), (687, 1), (710, 0), (711, 1), (727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0),
(4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), (8426, 0), (9000, 1), (9002, 2), (11021, 1),
(12350, 2), (12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1), (55203, 2), (63743, 1),
(64106, 2), (65039, 1), (65059, 0), (65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2),
(120831, 1), (262141, 2), (1114109, 1),
]
if o == 0xe or o == 0xf:
return 0
for num, wid in widths:
if o <= num:
return wid
return 1
def _coloumWidth(self, s: str):
res = 0
for ch in s:
res += self._getCharWidth(ord(ch))
return res
def _changeColumnWidth(self, s: str, len: int) -> str:
res = 0
sList = []
for ch in s:
res += self._getCharWidth(ord(ch))
if res <= len:
sList.append(ch)
return ''.join(sList)
def _resizePic(self, img: Image.Image, time: float):
return img.resize((int(img.size[0] * time), int(img.size[1] * time)))
def _findRaPic(self) -> str:
num = '10'
if self.playerRating < 1000:
num = '01'
elif self.playerRating < 2000:
num = '02'
elif self.playerRating < 4000:
num = '03'
elif self.playerRating < 7000:
num = '04'
elif self.playerRating < 10000:
num = '05'
elif self.playerRating < 12000:
num = '06'
elif self.playerRating < 13000:
num = '07'
elif self.playerRating < 14500:
num = '08'
elif self.playerRating < 15000:
num = '09'
return f'UI_CMN_DXRating_S_{num}.png'
def _drawRating(self, ratingBaseImg: Image.Image):
COLOUMS_RATING = [86, 100, 115, 130, 145]
theRa = self.playerRating
i = 4
while theRa:
digit = theRa % 10
theRa = theRa // 10
digitImg = Image.open(self.pic_dir + f'UI_NUM_Drating_{digit}.png').convert('RGBA')
digitImg = self._resizePic(digitImg, 0.6)
ratingBaseImg.paste(digitImg, (COLOUMS_RATING[i] - 2, 9), mask=digitImg.split()[3])
i = i - 1
return ratingBaseImg
def _drawBestList(self, img: Image.Image, sdBest: BestList, dxBest: BestList):
itemW = 131
itemH = 88
Color = [(69, 193, 36), (255, 186, 1), (255, 90, 102), (134, 49, 200), (217, 197, 233)]
levelTriagle = [(itemW, 0), (itemW - 27, 0), (itemW, 27)]
rankPic = 'D C B BB BBB A AA AAA S Sp SS SSp SSS SSSp'.split(' ')
comboPic = ' FC FCp AP APp'.split(' ')
imgDraw = ImageDraw.Draw(img)
titleFontName = 'assets/maimai/static/adobe_simhei.otf'
for num in range(0, len(sdBest)):
i = num // 7
j = num % 7
chartInfo = sdBest[num]
pngPath = self.cover_dir + f'{get_cover_len5_id(chartInfo.idNum)}.png'
if not os.path.exists(pngPath):
pngPath = self.cover_dir + '01000.png'
temp = Image.open(pngPath).convert('RGB')
temp = self._resizePic(temp, itemW / temp.size[0])
temp = temp.crop((0, (temp.size[1] - itemH) / 2, itemW, (temp.size[1] + itemH) / 2))
temp = temp.filter(ImageFilter.GaussianBlur(3))
temp = temp.point(lambda p: int(p * 0.72))
tempDraw = ImageDraw.Draw(temp)
tempDraw.polygon(levelTriagle, Color[chartInfo.diff])
font = ImageFont.truetype(titleFontName, 16, encoding='utf-8')
title = chartInfo.title
if self._coloumWidth(title) > 15:
title = self._changeColumnWidth(title, 12) + '...'
tempDraw.text((8, 8), title, 'white', font)
font = ImageFont.truetype(titleFontName, 12, encoding='utf-8')
tempDraw.text((7, 28), f'{"%.4f" % chartInfo.achievement}%', 'white', font)
rankImg = Image.open(self.pic_dir + f'UI_GAM_Rank_{rankPic[chartInfo.scoreId]}.png').convert('RGBA')
rankImg = self._resizePic(rankImg, 0.3)
temp.paste(rankImg, (72, 28), rankImg.split()[3])
if chartInfo.comboId:
comboImg = Image.open(self.pic_dir + f'UI_MSS_MBase_Icon_{comboPic[chartInfo.comboId]}_S.png').convert(
'RGBA')
comboImg = self._resizePic(comboImg, 0.45)
temp.paste(comboImg, (103, 27), comboImg.split()[3])
font = ImageFont.truetype('assets/maimai/static/adobe_simhei.otf', 12, encoding='utf-8')
tempDraw.text((8, 44), f'Base: {chartInfo.ds} -> {computeRa(chartInfo.ds, chartInfo.achievement)}', 'white',
font)
font = ImageFont.truetype('assets/maimai/static/adobe_simhei.otf', 18, encoding='utf-8')
tempDraw.text((8, 60), f'#{num + 1}', 'white', font)
recBase = Image.new('RGBA', (itemW, itemH), 'black')
recBase = recBase.point(lambda p: int(p * 0.8))
img.paste(recBase, (self.COLOUMS_IMG[j] + 5, self.ROWS_IMG[i + 1] + 5))
img.paste(temp, (self.COLOUMS_IMG[j] + 4, self.ROWS_IMG[i + 1] + 4))
for num in range(len(sdBest), sdBest.size):
i = num // 7
j = num % 7
temp = Image.open(self.cover_dir + f'01000.png').convert('RGB')
temp = self._resizePic(temp, itemW / temp.size[0])
temp = temp.crop((0, (temp.size[1] - itemH) / 2, itemW, (temp.size[1] + itemH) / 2))
temp = temp.filter(ImageFilter.GaussianBlur(1))
img.paste(temp, (self.COLOUMS_IMG[j] + 4, self.ROWS_IMG[i + 1] + 4))
for num in range(0, len(dxBest)):
i = num // 3
j = num % 3
chartInfo = dxBest[num]
pngPath = self.cover_dir + f'{get_cover_len5_id(chartInfo.idNum)}.png'
if not os.path.exists(pngPath):
pngPath = self.cover_dir + '01000.png'
temp = Image.open(pngPath).convert('RGB')
temp = self._resizePic(temp, itemW / temp.size[0])
temp = temp.crop((0, (temp.size[1] - itemH) / 2, itemW, (temp.size[1] + itemH) / 2))
temp = temp.filter(ImageFilter.GaussianBlur(3))
temp = temp.point(lambda p: int(p * 0.72))
tempDraw = ImageDraw.Draw(temp)
tempDraw.polygon(levelTriagle, Color[chartInfo.diff])
font = ImageFont.truetype(titleFontName, 14, encoding='utf-8')
title = chartInfo.title
if self._coloumWidth(title) > 13:
title = self._changeColumnWidth(title, 12) + '...'
tempDraw.text((8, 8), title, 'white', font)
font = ImageFont.truetype(titleFontName, 12, encoding='utf-8')
tempDraw.text((7, 28), f'{"%.4f" % chartInfo.achievement}%', 'white', font)
rankImg = Image.open(self.pic_dir + f'UI_GAM_Rank_{rankPic[chartInfo.scoreId]}.png').convert('RGBA')
rankImg = self._resizePic(rankImg, 0.3)
temp.paste(rankImg, (72, 28), rankImg.split()[3])
if chartInfo.comboId:
comboImg = Image.open(self.pic_dir + f'UI_MSS_MBase_Icon_{comboPic[chartInfo.comboId]}_S.png').convert(
'RGBA')
comboImg = self._resizePic(comboImg, 0.45)
temp.paste(comboImg, (103, 27), comboImg.split()[3])
font = ImageFont.truetype('assets/maimai/static/adobe_simhei.otf', 12, encoding='utf-8')
tempDraw.text((8, 44), f'Base: {chartInfo.ds} -> {chartInfo.ra}', 'white', font)
font = ImageFont.truetype('assets/maimai/static/adobe_simhei.otf', 18, encoding='utf-8')
tempDraw.text((8, 60), f'#{num + 1}', 'white', font)
recBase = Image.new('RGBA', (itemW, itemH), 'black')
recBase = recBase.point(lambda p: int(p * 0.8))
img.paste(recBase, (self.COLOUMS_IMG[j + 8] + 5, self.ROWS_IMG[i + 1] + 5))
img.paste(temp, (self.COLOUMS_IMG[j + 8] + 4, self.ROWS_IMG[i + 1] + 4))
for num in range(len(dxBest), dxBest.size):
i = num // 3
j = num % 3
temp = Image.open(self.cover_dir + f'01000.png').convert('RGB')
temp = self._resizePic(temp, itemW / temp.size[0])
temp = temp.crop((0, (temp.size[1] - itemH) / 2, itemW, (temp.size[1] + itemH) / 2))
temp = temp.filter(ImageFilter.GaussianBlur(1))
img.paste(temp, (self.COLOUMS_IMG[j + 8] + 4, self.ROWS_IMG[i + 1] + 4))
def draw(self):
splashLogo = Image.open(self.pic_dir + 'UI_CMN_TabTitle_MaimaiTitle_Ver214.png').convert('RGBA')
splashLogo = self._resizePic(splashLogo, 0.65)
self.img.paste(splashLogo, (10, 10), mask=splashLogo.split()[3])
ratingBaseImg = Image.open(self.pic_dir + self._findRaPic()).convert('RGBA')
ratingBaseImg = self._drawRating(ratingBaseImg)
ratingBaseImg = self._resizePic(ratingBaseImg, 0.85)
self.img.paste(ratingBaseImg, (240, 8), mask=ratingBaseImg.split()[3])
namePlateImg = Image.open(self.pic_dir + 'UI_TST_PlateMask.png').convert('RGBA')
namePlateImg = namePlateImg.resize((285, 40))
namePlateDraw = ImageDraw.Draw(namePlateImg)
font1 = ImageFont.truetype('assets/maimai/static/msyh.ttc', 28, encoding='unic')
namePlateDraw.text((12, 4), ' '.join(list(self.userName)), 'black', font1)
nameDxImg = Image.open(self.pic_dir + 'UI_CMN_Name_DX.png').convert('RGBA')
nameDxImg = self._resizePic(nameDxImg, 0.9)
namePlateImg.paste(nameDxImg, (230, 4), mask=nameDxImg.split()[3])
self.img.paste(namePlateImg, (240, 40), mask=namePlateImg.split()[3])
shougouImg = Image.open(self.pic_dir + 'UI_CMN_Shougou_Rainbow.png').convert('RGBA')
shougouDraw = ImageDraw.Draw(shougouImg)
font2 = ImageFont.truetype('assets/maimai/static/adobe_simhei.otf', 14, encoding='utf-8')
playCountInfo = f'SD: {self.sdRating} + DX: {self.dxRating} = {self.playerRating}'
shougouImgW, shougouImgH = shougouImg.size
playCountInfoW, playCountInfoH = shougouDraw.textsize(playCountInfo, font2)
textPos = ((shougouImgW - playCountInfoW - font2.getoffset(playCountInfo)[0]) / 2, 5)
shougouDraw.text((textPos[0] - 1, textPos[1]), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0] + 1, textPos[1]), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0], textPos[1] - 1), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0], textPos[1] + 1), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0] - 1, textPos[1] - 1), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0] + 1, textPos[1] - 1), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0] - 1, textPos[1] + 1), playCountInfo, 'black', font2)
shougouDraw.text((textPos[0] + 1, textPos[1] + 1), playCountInfo, 'black', font2)
shougouDraw.text(textPos, playCountInfo, 'white', font2)
shougouImg = self._resizePic(shougouImg, 1.05)
self.img.paste(shougouImg, (240, 83), mask=shougouImg.split()[3])
self._drawBestList(self.img, self.sdBest, self.dxBest)
authorBoardImg = Image.open(self.pic_dir + 'UI_CMN_MiniDialog_01.png').convert('RGBA')
authorBoardImg = self._resizePic(authorBoardImg, 0.35)
authorBoardDraw = ImageDraw.Draw(authorBoardImg)
authorBoardDraw.text((31, 28), ' Ported by\n Akaribot', 'black', font2)
self.img.paste(authorBoardImg, (1224, 19), mask=authorBoardImg.split()[3])
dxImg = Image.open(self.pic_dir + 'UI_RSL_MBase_Parts_01.png').convert('RGBA')
self.img.paste(dxImg, (988, 65), mask=dxImg.split()[3])
sdImg = Image.open(self.pic_dir + 'UI_RSL_MBase_Parts_02.png').convert('RGBA')
self.img.paste(sdImg, (865, 65), mask=sdImg.split()[3])
# self.img.show()
def getDir(self):
return self.img
def computeRa(ds: float, achievement: float) -> int:
baseRa = 22.4
if achievement < 50:
baseRa = 7.0
elif achievement < 60:
baseRa = 8.0
elif achievement < 70:
baseRa = 9.6
elif achievement < 75:
baseRa = 11.2
elif achievement < 80:
baseRa = 12.0
elif achievement < 90:
baseRa = 13.6
elif achievement < 94:
baseRa = 15.2
elif achievement < 97:
baseRa = 16.8
elif achievement < 98:
baseRa = 20.0
elif achievement < 99:
baseRa = 20.3
elif achievement < 99.5:
baseRa = 20.8
elif achievement < 100:
baseRa = 21.1
elif achievement < 100.5:
baseRa = 21.6
return math.floor(ds * (min(100.5, achievement) / 100) * baseRa)
async def generate(msg, payload) -> Tuple[Optional[Image.Image], bool]:
try:
resp = await post_url('https://www.diving-fish.com/api/maimaidxprober/query/player',
data=json.dumps(payload),
status_code=200,
headers={'Content-Type': 'application/json', 'accept': '*/*'}, fmt='json')
except ValueError as e:
if str(e).startswith('400'):
await msg.finish(msg.locale.t("maimai.message.user_not_found"))
if str(e).startswith('403'):
await msg.finish(msg.locale.t("maimai.message.forbidden"))
else:
await msg.finish(ErrorMessage(str(e)))
sd_best = BestList(35)
dx_best = BestList(15)
obj = resp
dx: List[Dict] = obj["charts"]["dx"]
sd: List[Dict] = obj["charts"]["sd"]
for c in sd:
sd_best.push(await ChartInfo.from_json(c))
for c in dx:
dx_best.push(await ChartInfo.from_json(c))
pic = DrawBest(sd_best, dx_best, obj["nickname"]).getDir()
return pic