798 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package beatleader
import (
"fmt"
"image"
"image/color"
"image/draw"
"log"
"math"
"os"
"strings"
"time"
"git.lxtend.com/lixiangwuxian/imagedd/font2img"
"git.lxtend.com/lixiangwuxian/imagedd/sprite"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/util"
)
type ScoreData struct {
ContextExtensions []ContextExtension `json:"contextExtensions"`
MyScore *string `json:"myScore"`
ValidContexts int `json:"validContexts"`
Leaderboard Leaderboard `json:"leaderboard"`
AccLeft float64 `json:"accLeft"`
AccRight float64 `json:"accRight"`
ID int `json:"id"`
BaseScore int `json:"baseScore"`
ModifiedScore int `json:"modifiedScore"`
Accuracy float64 `json:"accuracy"`
PlayerID string `json:"playerId"`
Pp float64 `json:"pp"`
BonusPp float64 `json:"bonusPp"`
PassPp float64 `json:"passPP"`
AccPp float64 `json:"accPP"`
TechPp float64 `json:"techPP"`
Rank int `json:"rank"`
Country string `json:"country"`
FcAccuracy float64 `json:"fcAccuracy"`
FcPp float64 `json:"fcPp"`
Weight float64 `json:"weight"`
Replay string `json:"replay"`
Modifiers string `json:"modifiers"`
BadCuts int `json:"badCuts"`
MissedNotes int `json:"missedNotes"`
BombCuts int `json:"bombCuts"`
WallsHit int `json:"wallsHit"`
Pauses int `json:"pauses"`
FullCombo bool `json:"fullCombo"`
Platform string `json:"platform"`
MaxCombo int `json:"maxCombo"`
MaxStreak *int `json:"maxStreak"`
HMD int `json:"hmd"`
Controller int `json:"controller"`
LeaderboardID string `json:"leaderboardId"`
Timeset string `json:"timeset"`
Timepost int64 `json:"timepost"`
ReplaysWatched int `json:"replaysWatched"`
PlayCount int `json:"playCount"`
LastTryTime int64 `json:"lastTryTime"`
Priority int `json:"priority"`
Player Player `json:"player"`
ScoreImprovement ScoreImprovement `json:"scoreImprovement"`
// RankVoting *string `json:"rankVoting"`
// Metadata *string `json:"metadata"`
Offsets Offsets `json:"offsets"`
}
type ContextExtension struct {
ID int `json:"id"`
PlayerID string `json:"playerId"`
Weight float64 `json:"weight"`
Rank int `json:"rank"`
BaseScore int `json:"baseScore"`
ModifiedScore int `json:"modifiedScore"`
Accuracy float64 `json:"accuracy"`
Pp float64 `json:"pp"`
PassPp float64 `json:"passPP"`
AccPp float64 `json:"accPP"`
TechPp float64 `json:"techPP"`
BonusPp float64 `json:"bonusPp"`
Modifiers string `json:"modifiers"`
Context int `json:"context"`
ScoreImprovement ScoreImprovement `json:"scoreImprovement"`
}
type ScoreImprovement struct {
ID int `json:"id"`
Timeset string `json:"timeset"`
Score int `json:"score"`
Accuracy float64 `json:"accuracy"`
Pp float64 `json:"pp"`
BonusPp float64 `json:"bonusPp"`
Rank int `json:"rank"`
AccRight float64 `json:"accRight"`
AccLeft float64 `json:"accLeft"`
AverageRankedAccuracy float64 `json:"averageRankedAccuracy"`
TotalPp float64 `json:"totalPp"`
TotalRank int `json:"totalRank"`
BadCuts int `json:"badCuts"`
MissedNotes int `json:"missedNotes"`
BombCuts int `json:"bombCuts"`
WallsHit int `json:"wallsHit"`
Pauses int `json:"pauses"`
}
type Leaderboard struct {
ID string `json:"id"`
Song Song `json:"song"`
Difficulty Difficulty `json:"difficulty"`
}
type Song struct {
ID string `json:"id"`
Hash string `json:"hash"`
Name string `json:"name"`
SubName string `json:"subName"`
Author string `json:"author"`
Mapper string `json:"mapper"`
MapperID int `json:"mapperId"`
CollaboratorIds *string `json:"collaboratorIds"`
CoverImage string `json:"coverImage"`
Bpm float64 `json:"bpm"`
Duration float64 `json:"duration"`
FullCoverImage *string `json:"fullCoverImage"`
}
type Difficulty struct {
ID int `json:"id"`
Value int `json:"value"`
Mode int `json:"mode"`
DifficultyName string `json:"difficultyName"`
ModeName string `json:"modeName"`
Status int `json:"status"`
ModifierValues ModifierValues `json:"modifierValues"`
// ModifiersRating *string `json:"modifiersRating"`
NominatedTime int64 `json:"nominatedTime"`
QualifiedTime int64 `json:"qualifiedTime"`
RankedTime int64 `json:"rankedTime"`
SpeedTags int `json:"speedTags"`
StyleTags int `json:"styleTags"`
FeatureTags int `json:"featureTags"`
Stars *float64 `json:"stars"`
PredictedAcc float64 `json:"predictedAcc"`
}
type ModifierValues struct {
ModifierID int `json:"modifierId"`
Da float64 `json:"da"`
Fs float64 `json:"fs"`
Sf float64 `json:"sf"`
// 其他修改器字段...
}
type Player struct {
ID string `json:"id"`
Name string `json:"name"`
Platform string `json:"platform"`
Avatar string `json:"avatar"`
Country string `json:"country"`
Alias *string `json:"alias"`
Bot bool `json:"bot"`
Pp float64 `json:"pp"`
Rank int `json:"rank"`
CountryRank int `json:"countryRank"`
Role string `json:"role"`
// Socials *string `json:"socials"`
ContextExtensions *string `json:"contextExtensions"`
}
type Offsets struct {
ID int `json:"id"`
Frames int `json:"frames"`
Notes int `json:"notes"`
Walls int `json:"walls"`
Heights int `json:"heights"`
Pauses int `json:"pauses"`
}
// 表示记录的数据,本地储存
type RecordDataLite struct {
ID int `json:"id" db:"id"`
ScoreID int `json:"scoreId" db:"score_id"`
BlID string `json:"blId" db:"bl_id"`
Name string `json:"name" db:"name"`
Country string `json:"country" db:"country"`
SongName string `json:"songName" db:"song_name"`
SongSubName string `json:"songSubName" db:"song_sub_name"`
SongAuthorName string `json:"songAuthorName" db:"song_author_name"`
SongHash string `json:"songHash" db:"song_hash"`
SongId string `json:"songId" db:"song_id"`
CoverImage string `json:"coverImage" db:"cover_image"`
DifficultyRaw string `json:"difficultyRaw" db:"difficulty_raw"`
Stars float64 `json:"stars" db:"stars"`
PP float64 `json:"pp" db:"pp"`
Weight float64 `json:"weight" db:"weight"`
Modifiers string `json:"modifiers" db:"modifiers"`
Multiplier float64 `json:"multiplier" db:"multiplier"`
Rank int `json:"rank" db:"rank"`
BadCuts int `json:"badCuts" db:"bad_cuts"`
MissedNotes int `json:"missedNotes" db:"missed_notes"`
MaxCombo int `json:"maxCombo" db:"max_combo"`
Score int `json:"score" db:"score"`
MaxScore int `json:"maxScore" db:"max_score"`
FullCombo bool `json:"fullCombo" db:"full_combo"`
DeviceHmd string `json:"deviceHmd" db:"device_hmd"`
DeviceControllerLeft string `json:"deviceControllerLeft" db:"device_controller_left"`
DeviceControllerRight string `json:"deviceControllerRight" db:"device_controller_right"`
GeneratedTime string `json:"generatedTime" db:"generated_time"`
}
func (r RecordDataLite) ToString() string {
formatedStrRanked := "%s%s 使用 %s 在 %s(%s) 的 %s 难度(%.1f🌟)中打到了全球排名第%dpp 为 %.2f,准度为 %s。"
formatedStrUnranked := "%s, %s 使用 %s 在 %s(%s) 的 %s 难度中打到了全球排名第%d准度为 %s。"
formatedStrWithoutDevice := "%s, %s 在 %s(%s) 的 %s 难度(%.1f🌟)中打到了全球排名第%dpp 为 %.2f,准度为 %s。"
formatedStrWithoutDeviceAndRank := "%s, %s 在 %s(%s) 的 %s 难度中打到了全球排名第%d准度为 %s。"
hardStr := r.DifficultyRaw
layout := "2006-01-02 15:04:05.999999999-07:00"
parsedTime, _ := time.Parse(layout, r.GeneratedTime)
duration := time.Since(parsedTime)
timeStr := timeConvert(duration)
if r.Stars == 0 && r.DeviceHmd != "" {
return fmt.Sprintf(formatedStrUnranked, timeStr, r.Name, r.DeviceHmd, r.SongName, r.SongId, hardStr, r.Rank, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else if r.Stars != 0 && r.DeviceHmd != "" {
return fmt.Sprintf(formatedStrRanked, timeStr, r.Name, r.DeviceHmd, r.SongName, r.SongId, hardStr, r.Stars, r.Rank, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else if r.Stars != 0 && r.DeviceHmd == "" {
return fmt.Sprintf(formatedStrWithoutDevice, timeStr, r.Name, r.SongName, r.SongId, hardStr, r.Stars, r.Rank, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else {
return fmt.Sprintf(formatedStrWithoutDeviceAndRank, timeStr, r.Name, r.SongName, r.SongId, hardStr, r.Rank, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
}
}
func timeConvert(duration time.Duration) string {
var result string
if duration.Hours() >= 24 {
days := int(duration.Hours() / 24)
result = fmt.Sprintf("%d天前", days)
} else if duration.Hours() >= 1 {
hours := int(duration.Hours())
result = fmt.Sprintf("%d小时前", hours)
} else if duration.Minutes() >= 1 {
minutes := int(duration.Minutes())
result = fmt.Sprintf("%d分钟前", minutes)
} else {
result = "刚刚"
}
return result
}
//用户信息
// ScoreStats 存储分数统计信息
type PlayerData struct {
MapperID int `json:"mapperId"`
Banned bool `json:"banned"`
Inactive bool `json:"inactive"`
// BanDescription *string `json:"banDescription"`
ExternalProfileURL string `json:"externalProfileUrl"`
RichBioTimeset int64 `json:"richBioTimeset"`
SpeedrunStart int64 `json:"speedrunStart"`
LinkedIDs LinkedIDs `json:"linkedIds"`
// History *string `json:"history"`
// Badges []string `json:"badges"`
// PinnedScores *string `json:"pinnedScores"`
// Changes []Change `json:"changes"`
AccPp float64 `json:"accPp"`
PassPp float64 `json:"passPp"`
TechPp float64 `json:"techPp"`
ScoreStats ScoreStats `json:"scoreStats"`
LastWeekPp float64 `json:"lastWeekPp"`
LastWeekRank int `json:"lastWeekRank"`
LastWeekCountryRank int `json:"lastWeekCountryRank"`
ExtensionID int `json:"extensionId"`
ID string `json:"id"`
Name string `json:"name"`
Platform string `json:"platform"`
Avatar string `json:"avatar"`
Country string `json:"country"`
// Alias *string `json:"alias"`
Bot bool `json:"bot"`
PP float64 `json:"pp"`
Rank int `json:"rank"`
CountryRank int `json:"countryRank"`
Role string `json:"role"`
// Socials []string `json:"socials"`
// ContextExtensions *string `json:"contextExtensions"`
// PatreonFeatures *string `json:"patreonFeatures"`
// ProfileSettings ProfileSettings `json:"profileSettings"`
// ClanOrder string `json:"clanOrder"`
// Clans []string `json:"clans"`
}
type LinkedIDs struct {
QuestID int `json:"questId"`
SteamID string `json:"steamId"`
OculusPCID string `json:"oculusPCId"`
}
type Change struct {
ID int64 `json:"id"`
Timestamp int64 `json:"timestamp"`
PlayerID string `json:"playerId"`
OldName string `json:"oldName"`
NewName string `json:"newName"`
OldCountry string `json:"oldCountry"`
NewCountry string `json:"newCountry"`
Changer *string `json:"changer"`
}
type ScoreStats struct {
ID int64 `json:"id"`
TotalScore int64 `json:"totalScore"`
TotalUnrankedScore int64 `json:"totalUnrankedScore"`
TotalRankedScore int64 `json:"totalRankedScore"`
LastScoreTime int64 `json:"lastScoreTime"`
LastUnrankedScoreTime int64 `json:"lastUnrankedScoreTime"`
LastRankedScoreTime int64 `json:"lastRankedScoreTime"`
AverageRankedAccuracy float64 `json:"averageRankedAccuracy"`
AverageWeightedRankedAccuracy float64 `json:"averageWeightedRankedAccuracy"`
AverageUnrankedAccuracy float64 `json:"averageUnrankedAccuracy"`
AverageAccuracy float64 `json:"averageAccuracy"`
MedianRankedAccuracy float64 `json:"medianRankedAccuracy"`
MedianAccuracy float64 `json:"medianAccuracy"`
TopRankedAccuracy float64 `json:"topRankedAccuracy"`
TopUnrankedAccuracy float64 `json:"topUnrankedAccuracy"`
TopAccuracy float64 `json:"topAccuracy"`
TopPp float64 `json:"topPp"`
TopBonusPP float64 `json:"topBonusPP"`
TopPassPP float64 `json:"topPassPP"`
TopAccPP float64 `json:"topAccPP"`
TopTechPP float64 `json:"topTechPP"`
PeakRank int `json:"peakRank"`
RankedMaxStreak int `json:"rankedMaxStreak"`
UnrankedMaxStreak int `json:"unrankedMaxStreak"`
MaxStreak int `json:"maxStreak"`
AverageLeftTiming float64 `json:"averageLeftTiming"`
AverageRightTiming float64 `json:"averageRightTiming"`
RankedPlayCount int `json:"rankedPlayCount"`
UnrankedPlayCount int `json:"unrankedPlayCount"`
TotalPlayCount int `json:"totalPlayCount"`
RankedImprovementsCount int `json:"rankedImprovementsCount"`
UnrankedImprovementsCount int `json:"unrankedImprovementsCount"`
TotalImprovementsCount int `json:"totalImprovementsCount"`
RankedTop1Count int `json:"rankedTop1Count"`
UnrankedTop1Count int `json:"unrankedTop1Count"`
Top1Count int `json:"top1Count"`
RankedTop1Score int64 `json:"rankedTop1Score"`
UnrankedTop1Score int64 `json:"unrankedTop1Score"`
Top1Score int64 `json:"top1Score"`
AverageRankedRank float64 `json:"averageRankedRank"`
AverageWeightedRankedRank float64 `json:"averageWeightedRankedRank"`
AverageUnrankedRank float64 `json:"averageUnrankedRank"`
AverageRank float64 `json:"averageRank"`
SspPlays int `json:"sspPlays"`
SsPlays int `json:"ssPlays"`
SpPlays int `json:"spPlays"`
SPlays int `json:"sPlays"`
APlays int `json:"aPlays"`
TopPlatform string `json:"topPlatform"`
TopHMD int `json:"topHMD"`
AllHMDs string `json:"allHMDs"`
TopPercentile float64 `json:"topPercentile"`
CountryTopPercentile float64 `json:"countryTopPercentile"`
DailyImprovements int `json:"dailyImprovements"`
AuthorizedReplayWatched int `json:"authorizedReplayWatched"`
AnonimusReplayWatched int `json:"anonimusReplayWatched"`
WatchedReplays int `json:"watchedReplays"`
}
type ProfileSettings struct {
ID int64 `json:"id"`
Bio *string `json:"bio"`
Message *string `json:"message"`
EffectName string `json:"effectName"`
ProfileAppearance string `json:"profileAppearance"`
Hue int `json:"hue"`
Saturation float64 `json:"saturation"`
LeftSaberColor *string `json:"leftSaberColor"`
RightSaberColor *string `json:"rightSaberColor"`
ProfileCover *string `json:"profileCover"`
StarredFriends string `json:"starredFriends"`
HorizontalRichBio bool `json:"horizontalRichBio"`
RankedMapperSort *string `json:"rankedMapperSort"`
ShowBots bool `json:"showBots"`
ShowAllRatings bool `json:"showAllRatings"`
ShowStatsPublic bool `json:"showStatsPublic"`
ShowStatsPublicPinned bool `json:"showStatsPublicPinned"`
}
type PlayerDataLite struct {
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Country string `json:"country" db:"country"`
Avatar string `json:"avatar" db:"avatar"`
Device string `json:"device" db:"device"`
PP float64 `json:"pp" db:"pp"`
Rank int `json:"rank" db:"rank"`
CountryRank int `json:"countryRank" db:"country_rank"`
TotalScore int64 `json:"totalScore" db:"total_score"`
TotalRankedScore int64 `json:"totalRankedScore" db:"total_ranked_score"`
AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"`
TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"`
RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"`
ReplaysWatched int `json:"replaysWatched" db:"replays_watched"`
GeneratedTime string `json:"generatedTime" db:"generated_time"`
}
func (p PlayerDataLite) IsDiffFrom(p2 PlayerDataLite) bool {
return p.TotalScore != p2.TotalScore ||
p.TotalRankedScore != p2.TotalRankedScore ||
p.AverageRankedAccuracy != p2.AverageRankedAccuracy ||
p.TotalPlayCount != p2.TotalPlayCount ||
p.RankedPlayCount != p2.RankedPlayCount ||
p.ReplaysWatched != p2.ReplaysWatched
}
func (p PlayerData) ToString() string {
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
if err != nil {
log.Default().Printf("下载头像失败url:%s,err:%v", p.Avatar, err)
}
defer os.Remove(filePath)
outFile, err := util.ResizeImageByMaxHeight2File(filePath, 20)
if err != nil {
log.Default().Printf("缩放头像失败url:%s,err:%v", p.Avatar, err)
}
picMsg := message.ImageMessage{
Type: message.TypeImage,
Data: message.ImageMessageData{
File: outFile,
},
}
formatedStr := "玩家 %s\n" +
picMsg.ToCQString() +
"区域 %s\n" +
"PP %.1f\n" +
"全球排名 %d\n" +
"区域排名 %d\n" +
"Ranked谱面均准 %.2f%%\n" +
"总游玩记数 %d\n" +
"Ranked谱面游玩记数 %d\n" +
"回放被观看次数 %d"
return fmt.Sprintf(formatedStr,
p.Name,
p.Country,
p.PP,
p.Rank,
p.CountryRank,
p.ScoreStats.AverageRankedAccuracy*100,
p.ScoreStats.TotalPlayCount,
p.ScoreStats.RankedPlayCount,
p.ScoreStats.WatchedReplays)
}
func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string {
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
if err != nil {
log.Default().Printf("下载头像失败url:%s,err:%v", p.Avatar, err)
}
defer os.Remove(filePath)
outFile, err := util.ResizeImageByMaxHeight2File(filePath, 20)
if err != nil {
log.Default().Printf("缩放头像失败url:%s,err:%v", p.Avatar, err)
}
picMsg := message.ImageMessage{
Type: message.TypeImage,
Data: message.ImageMessageData{
File: outFile,
},
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("玩家 %s\n", p.Name))
sb.WriteString(picMsg.ToCQString())
sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country))
// PP值
ppDiff := p.PP - lastDayQueryData.PP
if ppDiff == 0 {
sb.WriteString(fmt.Sprintf("PP %.1f\n", p.PP))
} else {
sb.WriteString(fmt.Sprintf("PP %.1f(%+.1f)\n", p.PP, ppDiff))
} // 全球排名
rankDiff := lastDayQueryData.Rank - p.Rank
if rankDiff == 0 {
sb.WriteString(fmt.Sprintf("全球排名 %d\n", p.Rank))
} else {
sb.WriteString(fmt.Sprintf("全球排名 %d(%+d)\n", p.Rank, rankDiff))
}
// 区域排名
countryRankDiff := lastDayQueryData.CountryRank - p.CountryRank
if countryRankDiff == 0 {
sb.WriteString(fmt.Sprintf("区域排名 %d\n", p.CountryRank))
} else {
sb.WriteString(fmt.Sprintf("区域排名 %d(%+d)\n", p.CountryRank, countryRankDiff))
}
// Ranked谱面均准
accDiff := (p.AverageRankedAccuracy - lastDayQueryData.AverageRankedAccuracy) * 100
if accDiff == 0 {
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%\n", p.AverageRankedAccuracy*100))
} else {
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%(%+.2f%%)\n", p.AverageRankedAccuracy*100, accDiff))
}
// 总游玩记数
totalPlayDiff := p.TotalPlayCount - lastDayQueryData.TotalPlayCount
if totalPlayDiff == 0 {
sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount))
} else {
sb.WriteString(fmt.Sprintf("总游玩记数 %d(%+d)\n", p.TotalPlayCount, totalPlayDiff))
}
// Ranked谱面游玩记数
rankedPlayDiff := p.RankedPlayCount - lastDayQueryData.RankedPlayCount
if rankedPlayDiff == 0 {
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d\n", p.RankedPlayCount))
} else {
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d(%+d)\n", p.RankedPlayCount, rankedPlayDiff))
}
// 回放被观看次数
sb.WriteString(fmt.Sprintf("回放被观看次数 %d", p.ReplaysWatched))
return sb.String()
}
func (p PlayerDataLite) LastDiffToImage(lastDayQueryData PlayerDataLite) string {
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
if err != nil {
log.Default().Printf("下载头像失败url:%s,err:%v", p.Avatar, err)
}
defer os.Remove(filePath)
// outFile, err := util.ResizeImageByMaxHeight2File(filePath, 20)
// if err != nil {
// log.Default().Printf("缩放头像失败url:%s,err:%v", p.Avatar, err)
// }
// picMsg := message.ImageMessage{
// Type: message.TypeImage,
// Data: message.ImageMessageData{
// File: outFile,
// },
// }
baseboard := sprite.NewNamedSpriteBoard()
avatar, err := util.ResizeImageByMaxHeight2Image(filePath, 20)
if err != nil {
log.Default().Printf("缩放头像失败url:%s,err:%v", p.Avatar, err)
}
avatarSpirit := sprite.Sprite{
Name: "avatar",
Image: avatar,
Index: 1,
}
baseboard.AddSprite(&avatarSpirit)
var sb strings.Builder
sb.WriteString(fmt.Sprintf("玩家 %s\n", p.Name))
sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country))
// PP值
ppDiff := p.PP - lastDayQueryData.PP
if ppDiff == 0 {
sb.WriteString(fmt.Sprintf("PP %.1f\n", p.PP))
} else {
sb.WriteString(fmt.Sprintf("PP %.1f(%+.1f)\n", p.PP, ppDiff))
} // 全球排名
rankDiff := lastDayQueryData.Rank - p.Rank
if rankDiff == 0 {
sb.WriteString(fmt.Sprintf("全球排名 %d\n", p.Rank))
} else {
sb.WriteString(fmt.Sprintf("全球排名 %d(%+d)\n", p.Rank, rankDiff))
}
// 区域排名
countryRankDiff := lastDayQueryData.CountryRank - p.CountryRank
if countryRankDiff == 0 {
sb.WriteString(fmt.Sprintf("区域排名 %d\n", p.CountryRank))
} else {
sb.WriteString(fmt.Sprintf("区域排名 %d(%+d)\n", p.CountryRank, countryRankDiff))
}
// Ranked谱面均准
accDiff := (p.AverageRankedAccuracy - lastDayQueryData.AverageRankedAccuracy) * 100
if accDiff == 0 {
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%\n", p.AverageRankedAccuracy*100))
} else {
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%(%+.2f%%)\n", p.AverageRankedAccuracy*100, accDiff))
}
// 总游玩记数
totalPlayDiff := p.TotalPlayCount - lastDayQueryData.TotalPlayCount
if totalPlayDiff == 0 {
sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount))
} else {
sb.WriteString(fmt.Sprintf("总游玩记数 %d(%+d)\n", p.TotalPlayCount, totalPlayDiff))
}
// Ranked谱面游玩记数
rankedPlayDiff := p.RankedPlayCount - lastDayQueryData.RankedPlayCount
if rankedPlayDiff == 0 {
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d\n", p.RankedPlayCount))
} else {
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d(%+d)\n", p.RankedPlayCount, rankedPlayDiff))
}
// 回放被观看次数
sb.WriteString(fmt.Sprintf("回放被观看次数 %d", p.ReplaysWatched))
text := sb.String()
textImg, err := font2img.RenderTextToTrimmedImage(nil, text, 12, color.Black, 0, 0)
if err != nil {
log.Default().Printf("渲染文字失败err:%v", err)
}
textSpirit := sprite.Sprite{
Name: "text",
Image: textImg,
Index: 2,
Position: image.Point{X: avatarSpirit.Position.X + avatarSpirit.Image.Bounds().Dx() + 3, Y: 0},
}
baseboard.AddSprite(&textSpirit)
totalWidth := textImg.Bounds().Dx() + avatar.Bounds().Dx()
totalHeight := math.Max(float64(textImg.Bounds().Dy()), float64(avatar.Bounds().Dy()))
background := image.NewRGBA(image.Rect(0, 0, int(totalWidth), int(totalHeight)))
draw.Draw(background, background.Bounds(), image.White, image.Point{}, draw.Src)
backgroundSpirit := sprite.Sprite{
Name: "background",
Image: background,
Index: 0,
}
baseboard.AddSprite(&backgroundSpirit)
if err := baseboard.SaveToPng(util.GenTempFilePath("cbl.png")); err != nil {
log.Default().Printf("保存图片失败err:%v", err)
}
return util.GenTempFilePath("cbl.png")
}
func GetControllerStr(controller int) string {
switch controller {
case 1:
return "Oculus Touch 手柄"
case 16:
return "Oculus Touch 2 手柄"
case 79:
return "Meta Quest 3 手柄"
case 256:
return "Quest 2 手柄"
case 2:
return "VIVE wands"
case 4:
return "VIVE 2 wands"
case 128:
return "VIVE cosmos controllers"
case 8:
return "WMR 手柄"
case 33:
return "Pico 手柄"
case 34:
return "Pico 手柄"
case 35:
return "VIVE Pro dudads"
case 37:
return "Miramar 手柄"
case 44:
return "disco 手柄"
case 61:
return "Quest Pro 手柄"
case 62:
return "VIVE tracker"
case 63:
return "VIVE tracker 2"
case 64:
return "Knuckles"
case 65:
return "nolo手柄"
case 66:
return "Pico phoenix"
case 67:
return "双手🙌"
case 68:
return "VIVE tracker 3"
case 75:
return "游戏手柄🎮"
case 76:
return "Joy-Con"
case 77:
return "Steam Deck"
case 78:
return "Etee"
}
return "神秘手柄"
}
func GetHMDStr(hmd int) string {
switch hmd {
case 256:
return "Quest 2"
case 512:
return "Quest 3"
case 64:
return "Valve Index"
case 513:
return "Quest 3S"
case 1:
return "Rift CV1"
case 2:
return "Vive"
case 60:
return "Pico 4"
case 61:
return "Quest Pro"
case 70:
return "PS VR2"
case 8:
return "WMR"
case 16:
return "Rift S"
case 65:
return "Controllable"
case 32:
return "Quest"
case 4:
return "Vive Pro"
case 35:
return "Vive Pro 2"
case 128:
return "Vive Cosmos"
case 36:
return "Vive Elite"
case 47:
return "Vive Focus"
case 38:
return "Pimax 8K"
case 39:
return "Pimax 5K"
case 40:
return "Pimax Artisan"
case 33:
return "Pico Neo 3"
case 34:
return "Pico Neo 2"
case 41:
return "HP Reverb"
case 42:
return "Samsung WMR"
case 43:
return "Qiyu Dream"
case 45:
return "Lenovo Explorer"
case 46:
return "Acer WMR"
case 66:
return "Bigscreen Beyond"
case 67:
return "NOLO Sonic"
case 68:
return "Hypereal"
case 48:
return "Arpara"
case 49:
return "Dell Visor"
case 71:
return "MeganeX VG1"
case 55:
return "Huawei VR"
case 56:
return "Asus WMR"
case 51:
return "Vive DVT"
case 52:
return "glasses20"
case 53:
return "Varjo"
case 69:
return "Varjo Aero"
case 54:
return "Vaporeon"
case 57:
return "Cloud XR"
case 58:
return "VRidge"
case 50:
return "e3"
case 59:
return "Medion Eraser"
case 37:
return "Miramar"
case 0:
return "Unknown headset"
case 44:
return "Disco"
default:
return "神秘头显"
}
}