feat: gacha

This commit is contained in:
bietiaop 2024-07-09 16:30:45 +08:00
parent e28fbd8472
commit af3b551a6a
33 changed files with 579 additions and 15 deletions

View file

@ -1,10 +1,9 @@
import { ZZZPlugin } from '../lib/plugin.js';
import _ from 'lodash';
import render from '../lib/render.js';
import { ZZZNoteResp } from '../model/note.js';
import { rulePrefix } from '../lib/common.js';
import { getAuthKey, getStoken } from '../lib/authkey.js';
import { updateGachaLog } from '../lib/gacha.js';
import { getAuthKey } from '../lib/authkey.js';
import { anaylizeGachaLog, updateGachaLog } from '../lib/gacha.js';
export class GachaLog extends ZZZPlugin {
constructor() {
@ -26,6 +25,10 @@ export class GachaLog extends ZZZPlugin {
reg: `^${rulePrefix}抽卡帮助$`,
fnc: 'gachaHelp',
},
{
reg: `^${rulePrefix}抽卡分析$`,
fnc: 'gachaLogAnalysis',
},
],
});
}
@ -87,4 +90,21 @@ export class GachaLog extends ZZZPlugin {
await this.reply(msg);
return false;
}
async gachaLogAnalysis() {
const uid = await this.getUID();
if (!uid) {
return false;
}
await this.getPlayerInfo();
const data = await anaylizeGachaLog(uid);
if (!data) {
await this.reply('未查询到抽卡记录,请先发送抽卡链接');
return false;
}
const result = {
data,
};
await render(this.e, 'gachalog/index.html', result);
}
}

View file

@ -1,12 +1,12 @@
import settings from '../settings.js';
import PartnerId2SpriteId from '../../resources/map/PartnerId2SpriteId.json';
import PartnerId2SpriteId from '../../resources/map/PartnerId2SpriteId.json?json';
/**
*
* @param {string} id
* @param {boolean} full 显示全称
* @param {boolean} en 是否为英文
* @returns string
* @returns string | null
*/
export const IDToCharName = (id, full = true, en = false) => {
const data = PartnerId2SpriteId?.[id];
@ -19,7 +19,7 @@ export const IDToCharName = (id, full = true, en = false) => {
/**
*
* @param {string} id
* @returns string
* @returns string | null
*/
export const IDToCharSprite = id => {
const data = PartnerId2SpriteId?.[id];
@ -29,7 +29,7 @@ export const IDToCharSprite = id => {
/**
* @param {string} name
* @returns string
* @returns string | null
*/
export const charNameToID = name => {
for (const [id, data] of Object.entries(PartnerId2SpriteId)) {
@ -40,7 +40,7 @@ export const charNameToID = name => {
/**
* @param {string} name
* @returns string
* @returns string | null
*/
export const charNameToSprite = name => {
for (const [_id, data] of Object.entries(PartnerId2SpriteId)) {
@ -51,19 +51,20 @@ export const charNameToSprite = name => {
/**
* @param {string} atlas
* @returns string
* @returns string | null
*/
export const atlasToName = atlas => {
export const atlasToName = _atlas => {
const atlas = settings.getConfig('atlas');
for (const [id, data] of Object.entries(atlas)) {
if (data.includes(atlas)) return id;
if (id === _atlas) return id;
if (data.includes(_atlas)) return id;
}
return null;
};
/**
* @param {string} atlas
* @returns string
* @returns string | null
*/
export const atlasToSprite = atlas => {
const atlas = settings.getConfig('atlas');
@ -75,7 +76,7 @@ export const atlasToSprite = atlas => {
/**
* @param {string} name
* @returns string
* @returns string | null
*/
export const atlasToID = name => {
const atlas = settings.getConfig('atlas');

View file

@ -1,4 +1,4 @@
import WeaponId2Sprite from '../../resources/map/WeaponId2Sprite.json';
import WeaponId2Sprite from '../../resources/map/WeaponId2Sprite.json?json';
/**
* @param {string} id

View file

@ -127,3 +127,104 @@ export async function updateGachaLog(authKey, uid) {
saveGachaLog(uid, previousLog);
return previousLog;
}
const RANK_MAP = {
4: 'S',
3: 'A',
2: 'B',
};
const HOMO_TAG = ['非到极致', '运气不好', '平稳保底', '小欧一把', '欧狗在此'];
const NORMAL_LIST = [
'「11号」',
'猫又',
'莱卡恩',
'丽娜',
'格莉丝',
'珂蕾妲',
'拘缚者',
'燃狱齿轮',
'嵌合编译器',
'钢铁肉垫',
'硫磺石',
'啜泣摇篮',
];
export async function anaylizeGachaLog(uid) {
const savedData = getGachaLog(uid);
if (!savedData) {
return null;
}
const result = [];
for (const name in savedData) {
const data = savedData[name].map(
item =>
new SingleGachaLog(
item.uid,
item.gacha_id,
item.gacha_type,
item.item_id,
item.count,
item.time,
item.name,
item.lang,
item.item_type,
item.rank_type,
item.id
)
);
const earliest = data[data.length - 1];
const latest = data[0];
const list = [];
let lastFive = `${data.length}`;
let preIndex = 0;
let luck = 0;
data.forEach((item, i) => {
let isUp = true;
if (item.rank_type === '4') {
if (NORMAL_LIST.includes(item.name)) {
isUp = false;
}
if (lastFive === `${data.length}`) {
lastFive = `${i + 1}`;
}
list.push({
...item,
rank_type_label: RANK_MAP[item.rank_type],
isUp: isUp,
});
if (list.length > 0) {
list[list.length - 1]['totalCount'] = i - preIndex;
}
preIndex = i;
}
if (i === data.length - 1 && list.length > 0) {
list[list.length - 1]['totalCount'] = i - preIndex;
}
});
const upCount = list.length;
const totalCount = data.length;
const fiveStars = list.length;
logger.mark('fiveStars', fiveStars);
logger.mark('totalCount', totalCount);
let timeRange = '还没有抽卡';
let avgFive = '-';
let avgUp = '-';
if (data.length > 0) {
timeRange = `${latest.time} ${earliest.time}`;
if (fiveStars > 0) avgFive = (totalCount / fiveStars).toFixed(1);
if (upCount > 0) avgUp = (totalCount / upCount).toFixed(1);
}
result.push({
name,
timeRange,
list,
lastFive,
fiveStars,
upCount,
totalCount,
avgFive,
avgUp,
});
}
return result;
}

View file

@ -48,7 +48,7 @@ export class SingleGachaLog {
equals(item) {
return (
this.uid === item.uid &&
this.gacha_id === item.gacha_id &&
this.id === item.id &&
this.gacha_type === this.gacha_type
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View file

@ -0,0 +1,216 @@
.card {
margin: 0 1em;
}
.card .user-info {
margin: 0 1em;
margin-bottom: 1.5em;
}
.card .title {
border-image-slice: 37 27 37 131 fill;
border-image-width: 1em 1em 1em 4.5em;
border-image-outset: 0em 0em 0em 0em;
border-image-repeat: stretch stretch;
min-height: 7em;
padding: 0.7em 2.3em 0.7em 3.7em;
display: flex;
align-items: center;
}
.card .title.t1 {
border-image-source: url("./images/bg1.png");
}
.card .title.t2 {
border-image-source: url("./images/bg2.png");
}
.card .title.t3 {
border-image-source: url("./images/bg3.png");
}
.card .title.t4 {
border-image-source: url("./images/bg4.png");
}
.card .title .info {
flex-grow: 1;
flex-shrink: 1;
}
.card .title .info .type {
display: flex;
align-items: flex-end;
gap: 0.1em;
}
.card .title .info .type .label {
font-size: 1.5em;
text-shadow: 0.05em 0.05em 0.03em rgba(0, 0, 0, 0.4);
}
.card .title .info .type .status {
font-size: 0.8em;
color: #e4e4e4;
margin-bottom: 0.2em;
background: rgba(0, 0, 0, 0.5);
padding: 0em 0.5em;
border-radius: 1em;
backdrop-filter: blur(0.3em);
}
.card .title .info .type .status .value {
color: rgb(128, 237, 84);
margin: 0 0.1em;
}
.card .title .info .time {
font-size: 0.6em;
color: #e4e4e4;
}
.card .title .info .analysis {
width: 15em;
display: flex;
background-color: rgba(0, 0, 0, 0.5);
margin-left: 1em;
margin-top: 0.3em;
margin-bottom: 0.2em;
border-radius: 2em;
padding: 0.1em 0.5em;
}
.card .title .info .analysis .item {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.card .title .info .analysis .item .value {
font-size: 1.3em;
}
.card .title .info .analysis .item .label {
font-size: 0.8em;
color: #e4e4e4;
}
.card .title .comment {
flex-grow: 0;
flex-shrink: 0;
}
.card .title .comment .icon {
width: 4.5em;
aspect-ratio: 1;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
margin-bottom: 0.5em;
}
.card .title .comment.e1 .icon {
background-image: url("./images/emoji/1.png");
}
.card .title .comment.e2 .icon {
background-image: url("./images/emoji/2.png");
}
.card .title .comment.e3 .icon {
background-image: url("./images/emoji/3.png");
}
.card .title .comment.e4 .icon {
background-image: url("./images/emoji/4.png");
}
.card .title .comment.e5 .icon {
background-image: url("./images/emoji/5.png");
}
.card .title .comment.e6 .icon {
background-image: url("./images/emoji/6.png");
}
.card .title .comment.e7 .icon {
background-image: url("./images/emoji/7.png");
}
.card .title .comment.e8 .icon {
background-image: url("./images/emoji/8.png");
}
.card .title .comment.e9 .icon {
background-image: url("./images/emoji/9.png");
}
.card .title .comment.e10 .icon {
background-image: url("./images/emoji/10.png");
}
.card .title .comment.e11 .icon {
background-image: url("./images/emoji/11.png");
}
.card .title .comment.e12 .icon {
background-image: url("./images/emoji/12.png");
}
.card .title .comment.e13 .icon {
background-image: url("./images/emoji/13.png");
}
.card .title .comment.e14 .icon {
background-image: url("./images/emoji/14.png");
}
.card .title .comment.e15 .icon {
background-image: url("./images/emoji/15.png");
}
.card .title .comment.e16 .icon {
background-image: url("./images/emoji/16.png");
}
.card .title .comment .label {
font-size: 1em;
text-align: center;
}
.card .list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1em;
padding: 0.5em 1.7em;
margin-bottom: 1.5em;
}
.card .list .item {
width: 100%;
position: relative;
border-radius: 0.5em;
overflow: hidden;
border: 0.2em solid #000000;
}
.card .list .item.up::after {
content: "";
display: block;
position: absolute;
top: 0.1em;
left: 0.2em;
width: 1.7em;
height: 1.7em;
background: url("./images/IconTabUP.png") no-repeat center center;
background-size: contain;
z-index: 1;
}
.card .list .item .rank {
position: absolute;
top: 0.2em;
right: 0.2em;
width: 1.5em;
aspect-ratio: 1;
background: url("./images/RANK_A.png") no-repeat center center;
background-size: contain;
color: white;
z-index: 2;
}
.card .list .item.rankS .rank {
background-image: url("./images/RANK_S.png");
}
.card .list .item.rankB .rank {
background-image: url("./images/RANK_B.png");
}
.card .list .item .image {
width: 100%;
aspect-ratio: 1.5;
background-color: #e2e2e2;
}
.card .list .item .image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
.card .list .item .count {
position: absolute;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(0.3em);
border-top-right-radius: 0.5em;
color: white;
text-align: center;
padding: 0 0.3em;
font-size: 0.9em;
}
/*# sourceMappingURL=index.css.map */

View file

@ -0,0 +1,54 @@
{{extend defaultLayout}}
{{block 'css'}}
<link rel="stylesheet" href="{{@sys.currentPath}}/index.css">
{{/block}}
{{block 'main'}}
<div class="card">
{{include sys.playerInfo}}
{{each data item i}}
<div class="title t{{i+1}}">
<div class="info">
<div class="type">
<div class="label">{{item.name}}</div>
<div class="status"><span class="value">{{item.lastFive}}</span>抽未出S级</div>
</div>
<div class="time">{{item.timeRange}}</div>
<div class="analysis">
<div class="item">
<div class="value">{{item.avgFive}}</div>
<div class="label">平均出金</div>
</div>
<div class="item">
<div class="value">{{item.avgUp}}</div>
<div class="label">平均UP</div>
</div>
<div class="item">
<div class="value">{{item.totalCount}}</div>
<div class="label">抽卡总数</div>
</div>
</div>
</div>
<!-- <div class="comment e1">
<div class="icon"></div>
<div class="label">平稳保底</div>
</div> -->
</div>
<div class="list">
{{each item.list inv j}}
<div class="item rankS {{inv.isUp && 'up'}}">
<div class="rank rankS"></div>
<div class="image">
<img src="./images/role_square_avatar_1011.png" alt="">
</div>
<div class="count">{{inv?.totalCount || '-'}}抽</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
{{/block}}

View file

@ -0,0 +1,172 @@
.card {
margin: 0 1em;
.user-info {
margin: 0 1em;
margin-bottom: 1.5em;
}
.title {
border-image-slice: 37 27 37 131 fill;
border-image-width: 1em 1em 1em 4.5em;
border-image-outset: 0em 0em 0em 0em;
border-image-repeat: stretch stretch;
min-height: 7em;
padding: 0.7em 2.3em 0.7em 3.7em;
display: flex;
align-items: center;
@for $i from 1 through 4 {
&.t#{$i} {
border-image-source: url('./images/bg#{$i}.png');
}
}
.info {
flex-grow: 1;
flex-shrink: 1;
.type {
display: flex;
align-items: flex-end;
gap: 0.1em;
.label {
font-size: 1.5em;
text-shadow: 0.05em 0.05em 0.03em rgba(0, 0, 0, 0.4);
}
.status {
font-size: 0.8em;
color: #e4e4e4;
margin-bottom: 0.2em;
background: rgba(0, 0, 0, 0.5);
padding: 0em 0.5em;
border-radius: 1em;
backdrop-filter: blur(0.3em);
.value {
color: rgb(128, 237, 84);
margin: 0 0.1em;
}
}
}
.time {
font-size: 0.6em;
color: #e4e4e4;
}
.analysis {
width: 15em;
display: flex;
background-color: rgba(0, 0, 0, 0.5);
margin-left: 1em;
margin-top: 0.3em;
margin-bottom: 0.2em;
border-radius: 2em;
padding: 0.1em 0.5em;
.item {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
align-items: center;
.value {
font-size: 1.3em;
}
.label {
font-size: 0.8em;
color: #e4e4e4;
}
}
}
}
.comment {
flex-grow: 0;
flex-shrink: 0;
.icon {
width: 4.5em;
aspect-ratio: 1;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
margin-bottom: 0.5em;
}
@for $i from 1 through 16 {
&.e#{$i} {
.icon {
background-image: url('./images/emoji/#{$i}.png');
}
}
}
.label {
font-size: 1em;
text-align: center;
}
}
}
.list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1em;
padding: 0.5em 1.7em;
margin-bottom: 1.5em;
.item {
width: 100%;
position: relative;
border-radius: 0.5em;
overflow: hidden;
border: 0.2em solid #000000;
&.up {
&::after {
content: '';
display: block;
position: absolute;
top: 0.1em;
left: 0.2em;
width: 1.7em;
height: 1.7em;
background: url('./images/IconTabUP.png') no-repeat center center;
background-size: contain;
z-index: 1;
}
}
.rank {
position: absolute;
top: 0.2em;
right: 0.2em;
width: 1.5em;
aspect-ratio: 1;
background: url('./images/RANK_A.png') no-repeat center center;
background-size: contain;
color: white;
z-index: 2;
}
&.rankS {
.rank {
background-image: url('./images/RANK_S.png');
}
}
&.rankB {
.rank {
background-image: url('./images/RANK_B.png');
}
}
.image {
width: 100%;
aspect-ratio: 1.5;
background-color: #e2e2e2;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
}
.count {
position: absolute;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(0.3em);
border-top-right-radius: 0.5em;
color: white;
text-align: center;
padding: 0 0.3em;
font-size: 0.9em;
}
}
}
}