角色立绘支持随机,用于面板场景

This commit is contained in:
Kokomi 2022-11-30 04:55:50 +08:00
parent 5fb7da70ae
commit 6eabd3f0cb
16 changed files with 214 additions and 200 deletions

1
.gitignore vendored
View File

@ -17,4 +17,3 @@
/config/cfg.js /config/cfg.js
/resources/profile/super-character/* /resources/profile/super-character/*
/resources/profile/normal-character/* /resources/profile/normal-character/*
!/resources/profile/super-character/达达利亚.webp

View File

@ -1,16 +1,17 @@
# 2.1.3 # 2.1.4
* 角色立绘支持随机,用于面板场景
* 图像支持webp及png格式
* 普通立绘:**resources/profile/normal-character/**
* 彩蛋立绘(满命/ACE/三皇冠)**resources/profile/super-character/**
* 单张立绘请放置在普通&彩蛋目录下,以**角色全名**为**文件名**,例如**刻晴.webp**
* 如需多张随机,请在普通&彩蛋目录下,以**角色全名**为**目录**名,任意文件名为文件名,例如 **刻晴/1.png**
# 2.1.1~2.1.3
* 增加面板替换功能,可通过命令更换面板的圣遗物、武器、天赋命座等,用于伤害计算 * 增加面板替换功能,可通过命令更换面板的圣遗物、武器、天赋命座等,用于伤害计算
* `#雷神面板换稻光换90级满命` / `#刻晴面板换雷神圣遗物` 等命令 * `#雷神面板换稻光换90级满命` / `#刻晴面板换雷神圣遗物` 等命令
* 更多命令参见 `#面板帮助`,请根据需求吟唱。后续会提供更细致的咒语详解 * 更多命令参见 `#面板帮助`,请根据需求吟唱。后续会提供更细致的咒语详解
* 角色立绘支持随机,用于面板、图鉴等场景
* 立绘可放置在 **resources/profile/randomMode-character/角色名/** 目录下webp格式
* 若目录获取到图片则不会使用普通立绘和彩蛋立绘
# 2.1.1~2.1.2
* 角色普通立绘支持自定义,用于面板、图鉴等场景
* 立绘可放置在 **resources/profile/normal-character/** 目录下webp格式
* 由于缓存新增的角色图需要重启bot生效已有图像替换无需重启
* 去除插件内自带的V2/V3兼容逻辑使用runtime进行V2/V3兼容如使用遇到问题请升级至最新版Yunzai * 去除插件内自带的V2/V3兼容逻辑使用runtime进行V2/V3兼容如使用遇到问题请升级至最新版Yunzai
* V3-Yunzai官方Yunzai最新版本 * V3-Yunzai官方Yunzai最新版本
* V2-Yunzai喵版V2-Yunzai2.2.3版本。其余分值维护的V2-Yunzai可合并2.2.3版本 * V2-Yunzai喵版V2-Yunzai2.2.3版本。其余分值维护的V2-Yunzai可合并2.2.3版本
@ -52,8 +53,6 @@
* 优化character的进入判定逻辑防止一些额外的log触发 * 优化character的进入判定逻辑防止一些额外的log触发
* 角色相关命令在V3下会联合V3的角色别名一同查询 * 角色相关命令在V3下会联合V3的角色别名一同查询
* `#深渊组队`使用新版胡桃API进行组队信息获取 * `#深渊组队`使用新版胡桃API进行组队信息获取
* 角色面板彩蛋图(满命/三皇冠/ACE 任一触发)支持自定义
* 自定义图像可放置在 **resources/profile/super-character/** 目录下
* 增加命令 `#最强排行`、`#最高分排行` 查看群排行 * 增加命令 `#最强排行`、`#最高分排行` 查看群排行
* 增加莱依拉的伤害计算及圣遗物评分权重 * 增加莱依拉的伤害计算及圣遗物评分权重

View File

@ -43,7 +43,7 @@ export async function profileArtis (e) {
return await Common.render('character/artis-mark', { return await Common.render('character/artis-mark', {
uid, uid,
elem: char.elem, elem: char.elem,
splash: char.getImgs(profile.costume).splash, splash: char.getImgs(profile.costume).splash0,
data: profile, data: profile,
costume: profile.costume ? '2' : '', costume: profile.costume ? '2' : '',
artisDetail, artisDetail,

View File

@ -96,7 +96,6 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
let artisDetail = profile.getArtisMark() let artisDetail = profile.getArtisMark()
let artisKeyTitle = ProfileArtis.getArtisKeyTitle() let artisKeyTitle = ProfileArtis.getArtisKeyTitle()
let imgs = char.getImgs(profile.costume) let imgs = char.getImgs(profile.costume)
imgs.img = imgs.isRandom ? imgs.randomImg.path + imgs.randomImg.urls[lodash.random(0, imgs.randomImg.urls.length - 1)] : imgs.splash
// 渲染图像 // 渲染图像
return await Common.render('character/profile-detail', { return await Common.render('character/profile-detail', {
save_id: uid, save_id: uid,

View File

@ -38,7 +38,7 @@ const ProfileChange = {
return false return false
} }
msg = msg.toLowerCase().replace(/uid ?:? ?/, '') msg = msg.toLowerCase().replace(/uid ?:? ?/, '')
let regRet = /^#*(\d{9})?(.+?)(详细|详情|面板|面版|圣遗物|伤害[1-7]?|换)\s*(\d{9})?(.+)/.exec(msg) let regRet = /^#*(\d{9})?(.+?)(详细|详情|面板|面版|圣遗物|伤害[1-7]?)?\s*(\d{9})?[变换改](.+)/.exec(msg)
if (!regRet || !regRet[2]) { if (!regRet || !regRet[2]) {
return false return false
} }

View File

@ -197,15 +197,28 @@ class Character extends Base {
} }
let costumeCfg = [this.checkCostume(costume[0]) ? '2' : '', costume[1] || 'normal'] let costumeCfg = [this.checkCostume(costume[0]) ? '2' : '', costume[1] || 'normal']
let cacheId = `costume${costumeCfg.join('')}` let cacheId = `costume${costume[0]}`
if (!this._imgs) { if (!this._imgs) {
this._imgs = {} this._imgs = {}
} }
if (this._imgs[cacheId]) { if (!this._imgs[cacheId]) {
return this._imgs[cacheId]
}
this._imgs[cacheId] = CharImg.getImgs(this.name, costumeCfg, this.isTraveler ? this.elem : '', this.source === 'amber' ? 'png' : 'webp') this._imgs[cacheId] = CharImg.getImgs(this.name, costumeCfg, this.isTraveler ? this.elem : '', this.source === 'amber' ? 'png' : 'webp')
return this._imgs[cacheId]
}
let ret = this._imgs[cacheId]
let nPath = `meta/character/${this.name}`
if (costumeCfg[1] === 'super') {
ret.splash0 = CharImg.getRandomImg(
[`profile/super-character/${this.name}`, `profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash0.webp`, `${nPath}/imgs/splash${costumeCfg[0]}.webp`, `/${nPath}/imgs/splash.webp`]
)
} else {
ret.splash0 = CharImg.getRandomImg(
[`profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash${costumeCfg[0]}.webp`, `/${nPath}/imgs/splash.webp`]
)
}
return ret
} }
// 获取详情数据 // 获取详情数据

View File

@ -5,6 +5,7 @@ import fs from 'fs'
import lodash from 'lodash' import lodash from 'lodash'
import sizeOf from 'image-size' import sizeOf from 'image-size'
const rPath = `${process.cwd()}/plugins/miao-plugin/resources`
const CharImg = { const CharImg = {
// 获取角色的插画 // 获取角色的插画
@ -53,6 +54,33 @@ const CharImg = {
return ret return ret
}, },
getRandomImg (imgPaths, defImgs = []) {
for (let imgPath of imgPaths) {
let ret = []
for (let type of ['webp', 'png']) {
if (fs.existsSync(`${rPath}/${imgPath}.${type}`)) {
ret.push(imgPath + '.webp')
}
}
if (fs.existsSync(`${rPath}/${imgPath}`)) {
let imgs = fs.readdirSync(`${rPath}/${imgPath}`).filter((file) => {
return /\.(png|webp)$/.test(file)
})
for (let img of imgs) {
ret.push(`${imgPath}/${img}`)
}
}
if (ret.length > 0) {
return lodash.sample(ret)
}
}
for (let defImg of defImgs) {
if (fs.existsSync(`${rPath}/${defImg}`)) {
return defImg
}
}
},
// 获取角色的图像资源数据 // 获取角色的图像资源数据
getImgs (name, costumeCfg = '', travelerElem = '', fileType = 'webp') { getImgs (name, costumeCfg = '', travelerElem = '', fileType = 'webp') {
if (!lodash.isArray(costumeCfg)) { if (!lodash.isArray(costumeCfg)) {
@ -62,7 +90,6 @@ const CharImg = {
if (!['空', '荧', '旅行者'].includes(name)) { if (!['空', '荧', '旅行者'].includes(name)) {
travelerElem = '' travelerElem = ''
} }
const rPath = `${process.cwd()}/plugins/miao-plugin/resources`
const nPath = `/meta/character/${name}/` const nPath = `/meta/character/${name}/`
const tPath = `/meta/character/旅行者/${travelerElem}/` const tPath = `/meta/character/旅行者/${travelerElem}/`
let add = (key, path, path2) => { let add = (key, path, path2) => {
@ -78,31 +105,8 @@ const CharImg = {
add('face', 'imgs/face', `imgs/face${costumeCfg[0]}`) add('face', 'imgs/face', `imgs/face${costumeCfg[0]}`)
add('side', 'imgs/side', `imgs/side${costumeCfg[0]}`) add('side', 'imgs/side', `imgs/side${costumeCfg[0]}`)
add('gacha', 'imgs/gacha') add('gacha', 'imgs/gacha')
// 随机角色面板图
imgs.isRandom = false
let randomMode = true // 随机模式开关
let randomImgPath = `${rPath}/profile/randomMode-character/${name}/`
if (randomMode && fs.existsSync(randomImgPath)) {
let imgUrls = fs.readdirSync(randomImgPath).filter((fileUrl) => {
return fileUrl.includes('.webp')
})
if (imgUrls.length != 0) {
imgs.randomImg = {}
imgs.randomImg.path = `profile/randomMode-character/${name}/`
imgs.randomImg.urls = imgUrls
imgs.isRandom = true
}
}
// 检查彩蛋自定义
if (costumeCfg[1] === 'super' && fs.existsSync(`${rPath}/profile/super-character/${name}.webp`)) {
imgs.splash = `profile/super-character/${name}.webp`
} else if (costumeCfg[1] === 'super' && fs.existsSync(`${rPath}/${nPath}/imgs/splash0.webp`)) {
imgs.splash = `${nPath}imgs/splash0.webp`
} else if (fs.existsSync(`${rPath}/profile/normal-character/${name}.webp`)) {
imgs.splash = `profile/normal-character/${name}.webp`
} else {
add('splash', 'imgs/splash', `imgs/splash${costumeCfg[0]}`) add('splash', 'imgs/splash', `imgs/splash${costumeCfg[0]}`)
} // 检查彩蛋自定义
tAdd('card', 'imgs/card') tAdd('card', 'imgs/card')
tAdd('banner', 'imgs/banner') tAdd('banner', 'imgs/banner')
for (let i = 1; i <= 6; i++) { for (let i = 1; i <= 6; i++) {

View File

@ -14,7 +14,7 @@ const CharArtis = {
let def = function (attrWeight) { let def = function (attrWeight) {
let title = [] let title = []
let weight = lodash.extend({}, attrWeight || usefulAttr[char.name] || { atk: 75, cpct: 100, cdmg: 100 }) let weight = lodash.extend({}, attrWeight || usefulAttr[char.name] || { atk: 75, cpct: 100, cdmg: 100, dmg: 100 })
let check = (key, max = 75, maxPlus = 75, isWeapon = true) => { let check = (key, max = 75, maxPlus = 75, isWeapon = true) => {
let original = weight[key] || 0 let original = weight[key] || 0
if (original < max) { if (original < max) {

View File

@ -11,7 +11,7 @@
{{block 'main'}} {{block 'main'}}
<div class="basic"> <div class="basic">
<div class="main-pic" <div class="main-pic"
style="background-image:url({{_res_path}}{{imgs.img}})"></div> style="background-image:url({{_res_path}}{{imgs.splash0}})"></div>
<div class="detail"> <div class="detail">
<div class="char-name">{{data.name}}</div> <div class="char-name">{{data.name}}</div>
<div class="char-lv">UID {{uid}} - Lv.{{data.level}} <div class="char-lv">UID {{uid}} - Lv.{{data.level}}

View File

@ -9,10 +9,10 @@ body {
color: #1e1f20; color: #1e1f20;
transform: scale(1.3); transform: scale(1.3);
transform-origin: 0 0; transform-origin: 0 0;
width: 520px; width: 600px;
} }
.container { .container {
width: 520px; width: 600px;
padding: 10px 0 10px 0; padding: 10px 0 10px 0;
background-size: 100% 100%; background-size: 100% 100%;
} }

View File

@ -14,11 +14,11 @@ body {
color: #1e1f20; color: #1e1f20;
transform: scale(1.3); transform: scale(1.3);
transform-origin: 0 0; transform-origin: 0 0;
width: 520px; width: 600px;
} }
.container { .container {
width: 520px; width: 600px;
padding: 10px 0 10px 0; padding: 10px 0 10px 0;
background-size: 100% 100%; background-size: 100% 100%;

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB