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

410 lines
17 KiB
Python
Raw Normal View History

2022-07-12 17:27:34 +00:00
import os
import math
from typing import Optional, Dict, List, Tuple
import aiohttp
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from .maimaidx_music import get_cover_len4_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
2022-07-12 17:32:29 +00:00
async def from_json(cls, data):
2022-07-12 17:27:34 +00:00
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_len4_id(chartInfo.idNum)}.png'
if not os.path.exists(pngPath):
pngPath = self.cover_dir + '1000.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'1000.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_len4_id(chartInfo.idNum)}.png'
if not os.path.exists(pngPath):
pngPath = self.cover_dir + '1000.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'1000.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)
2022-07-12 17:39:19 +00:00
authorBoardDraw.text((31, 28), ' Generated By\nXybBot & Chiyuki\nPorted by Akaribot', 'black', font2)
2022-07-12 17:27:34 +00:00
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 generate50(payload: Dict) -> Tuple[Optional[Image.Image], bool]:
async with aiohttp.request("POST", "https://www.diving-fish.com/api/maimaidxprober/query/player", json=payload) as resp:
if resp.status == 400:
return None, 400
if resp.status == 403:
return None, 403
sd_best = BestList(35)
dx_best = BestList(15)
obj = await resp.json()
dx: List[Dict] = obj["charts"]["dx"]
sd: List[Dict] = obj["charts"]["sd"]
for c in sd:
2022-07-12 17:32:29 +00:00
sd_best.push(await ChartInfo.from_json(c))
2022-07-12 17:27:34 +00:00
for c in dx:
2022-07-12 17:32:29 +00:00
dx_best.push(await ChartInfo.from_json(c))
2022-07-12 17:27:34 +00:00
pic = DrawBest(sd_best, dx_best, obj["nickname"]).getDir()
return pic, 0