优化
This commit is contained in:
BIN
client/web/plugins/com.msgbyte.genshin/assets/icon.jpg
Normal file
BIN
client/web/plugins/com.msgbyte.genshin/assets/icon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
12
client/web/plugins/com.msgbyte.genshin/manifest.json
Normal file
12
client/web/plugins/com.msgbyte.genshin/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"label": "Genshin Toolbox",
|
||||
"label.zh-CN": "原神工具箱",
|
||||
"name": "com.msgbyte.genshin",
|
||||
"url": "/plugins/com.msgbyte.genshin/index.js",
|
||||
"icon": "/plugins/com.msgbyte.genshin/assets/icon.jpg",
|
||||
"version": "0.0.0",
|
||||
"author": "msgbyte",
|
||||
"description": "Add Genshin-related entertainment capabilities to Tailchat",
|
||||
"description.zh-CN": "为Tailchat增加原神相关的娱乐能力",
|
||||
"requireRestart": true
|
||||
}
|
||||
13
client/web/plugins/com.msgbyte.genshin/package.json
Normal file
13
client/web/plugins/com.msgbyte.genshin/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@plugins/com.msgbyte.genshin",
|
||||
"main": "src/index.tsx",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"genshin-gacha-kit": "^1.1.0",
|
||||
"html-react-parser": "^1.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react": "18.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { OfficialGachaPoolItem } from 'genshin-gacha-kit';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ItemRoot = styled.div`
|
||||
position: relative;
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
line-height: 44px;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
export const GachaPoolItem: React.FC<{
|
||||
items: OfficialGachaPoolItem[];
|
||||
}> = React.memo((props) => {
|
||||
return (
|
||||
<div>
|
||||
{props.items.map((i) => (
|
||||
<ItemRoot key={i.item_id}>
|
||||
<img src={i.item_img} />
|
||||
|
||||
<div className="text">{i.item_name}</div>
|
||||
</ItemRoot>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
GachaPoolItem.displayName = 'GachaPoolItem';
|
||||
@@ -0,0 +1,38 @@
|
||||
import { AppWishResult } from 'genshin-gacha-kit';
|
||||
import React from 'react';
|
||||
import { WishResultText } from './WishResultText';
|
||||
|
||||
interface GachaResultProps {
|
||||
gachaResult: AppWishResult;
|
||||
withCount: boolean;
|
||||
}
|
||||
export const GachaResult: React.FC<GachaResultProps> = React.memo((props) => {
|
||||
const { gachaResult, withCount } = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ color: '#c17a4e' }}>
|
||||
<WishResultText
|
||||
label="5星"
|
||||
items={gachaResult.ssr}
|
||||
withCount={withCount}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ color: '#865cad' }}>
|
||||
<WishResultText
|
||||
label="4星"
|
||||
items={gachaResult.sr}
|
||||
withCount={withCount}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<WishResultText
|
||||
label="3星"
|
||||
items={gachaResult.r}
|
||||
withCount={withCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
GachaResult.displayName = 'GachaResult';
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ModalWrapper } from '@capital/common';
|
||||
import { AppGachaItem } from 'genshin-gacha-kit';
|
||||
import React from 'react';
|
||||
import { GachaResult } from './GachaResult';
|
||||
|
||||
export const WishResultModal: React.FC<{ items: AppGachaItem[] }> = React.memo(
|
||||
({ items }) => {
|
||||
return (
|
||||
<ModalWrapper title="抽卡结果">
|
||||
<GachaResult
|
||||
gachaResult={{
|
||||
ssr: items.filter((i) => i.rarity === 5),
|
||||
sr: items.filter((i) => i.rarity === 4),
|
||||
r: items.filter((i) => i.rarity === 3),
|
||||
}}
|
||||
withCount={false}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
WishResultModal.displayName = 'WishResultModal';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { AppGachaItem } from 'genshin-gacha-kit';
|
||||
import { getAppGachaItemText } from '../utils';
|
||||
import React from 'react';
|
||||
|
||||
function pickName(item: AppGachaItem) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
export const WishResultText: React.FC<{
|
||||
label: string;
|
||||
items: AppGachaItem[];
|
||||
withCount: boolean;
|
||||
}> = React.memo(({ label, items, withCount }) => {
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{label}:{' '}
|
||||
{items.map(withCount ? getAppGachaItemText : pickName).join(', ')}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
WishResultText.displayName = 'WishResultText';
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useAsync } from '@capital/common';
|
||||
import { Divider, Button, Space } from '@capital/component';
|
||||
import React from 'react';
|
||||
import { util } from 'genshin-gacha-kit';
|
||||
import { GenshinRichtext } from '../../components/GenshinRichtext';
|
||||
import { GachaPoolItem } from './GachaPoolItem';
|
||||
import { useWish } from './useWish';
|
||||
import { GachaResult } from './GachaResult';
|
||||
|
||||
export const GachaPool: React.FC<{
|
||||
gachaId: string;
|
||||
}> = React.memo((props) => {
|
||||
const { value: poolData } = useAsync(() => {
|
||||
return util.getGachaData(props.gachaId);
|
||||
}, [props.gachaId]);
|
||||
const { handleGacha, gachaResult, gachaCount } = useWish(poolData);
|
||||
|
||||
if (!poolData) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{poolData.banner}</div>
|
||||
|
||||
<div>{poolData.date_range}</div>
|
||||
<div className="gacha-pool">
|
||||
<GachaPoolItem items={poolData.r5_up_items ?? []} />
|
||||
<GachaPoolItem items={poolData.r4_up_items ?? []} />
|
||||
</div>
|
||||
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => handleGacha(1)}>
|
||||
模拟单抽
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => handleGacha(10)}>
|
||||
模拟十连
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
{gachaCount > 0 && (
|
||||
<div>
|
||||
<div>已抽: {gachaCount} 次</div>
|
||||
|
||||
<GachaResult gachaResult={gachaResult} withCount={true} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<GenshinRichtext raw={poolData.content} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
GachaPool.displayName = 'GachaPool';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { openModal, showToasts } from '@capital/common';
|
||||
import {
|
||||
AppWishResult,
|
||||
GenshinGachaKit,
|
||||
OfficialGachaPool,
|
||||
util,
|
||||
} from 'genshin-gacha-kit';
|
||||
import React from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { openFullScreenVideo } from '../../utils/openFullScreenVideo';
|
||||
import { wishVideoUrl } from '../consts';
|
||||
import { parseResultType } from '../utils';
|
||||
import { WishResultModal } from './WishResultModal';
|
||||
|
||||
/**
|
||||
* 祈愿
|
||||
* @param poolData 卡池信息
|
||||
*/
|
||||
export function useWish(poolData: OfficialGachaPool) {
|
||||
const [gachaResult, setGachaResult] = useState<AppWishResult>({
|
||||
ssr: [],
|
||||
sr: [],
|
||||
r: [],
|
||||
});
|
||||
const [gachaCount, setGachaCount] = useState<number>(0);
|
||||
|
||||
const gachaKit = useMemo(() => {
|
||||
return poolData
|
||||
? new GenshinGachaKit(util.poolStructureConverter(poolData))
|
||||
: null;
|
||||
}, [poolData]);
|
||||
const handleGacha = useCallback(
|
||||
(num) => {
|
||||
if (!gachaKit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = gachaKit.multiWish(num);
|
||||
|
||||
openFullScreenVideo(wishVideoUrl[parseResultType(res)]).then(() => {
|
||||
// showToasts('抽卡结果: ' + res.map((item) => item.name).join(','));
|
||||
|
||||
openModal(<WishResultModal items={res} />);
|
||||
|
||||
setGachaCount(gachaKit.getCounter('total') as number);
|
||||
setGachaResult(JSON.parse(JSON.stringify(gachaKit.getResult())));
|
||||
});
|
||||
},
|
||||
[gachaKit]
|
||||
);
|
||||
|
||||
return { handleGacha, gachaResult, gachaCount };
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 视频来源于 https://github.com/uzair-ashraf/genshin-impact-wish-simulator
|
||||
*/
|
||||
export const wishVideoUrl = {
|
||||
'5star': 'https://tailchat.moonrailgun.com/genshin/5starwish.webm',
|
||||
'4star': 'https://tailchat.moonrailgun.com/genshin/4starwish.webm',
|
||||
'5star-single':
|
||||
'https://tailchat.moonrailgun.com/genshin/5starwish-single.webm',
|
||||
'4star-single':
|
||||
'https://tailchat.moonrailgun.com/genshin/4starwish-single.webm',
|
||||
'3star-single':
|
||||
'https://tailchat.moonrailgun.com/genshin/3starwish-single.webm',
|
||||
} as const;
|
||||
@@ -0,0 +1,26 @@
|
||||
.plugin-genshin-panel {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.gacha-title {
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gacha-pool {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
> div {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Translate } from '../translate';
|
||||
import { OfficialGachaIndex, OfficialGachaType, util } from 'genshin-gacha-kit';
|
||||
import { useAsync } from '@capital/common';
|
||||
import { PillTabs, LoadingSpinner } from '@capital/component';
|
||||
import { GachaPool } from './GachaPool';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import './index.less';
|
||||
|
||||
const GenshinPanel: React.FC = React.memo(() => {
|
||||
const { value: gachaList, loading } = useAsync(async () => {
|
||||
const gacha = await util.getGachaIndex();
|
||||
const dict = _groupBy(gacha, 'gacha_type') as unknown as Record<
|
||||
keyof OfficialGachaType,
|
||||
OfficialGachaIndex[]
|
||||
>;
|
||||
|
||||
// 顺序: 角色 -> 武器 -> 常驻 -> 新手
|
||||
return [
|
||||
...(dict['301'] ?? []),
|
||||
...(dict['302'] ?? []),
|
||||
...(dict['200'] ?? []),
|
||||
...(dict['100'] ?? []),
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="plugin-genshin-panel">
|
||||
<div className="gacha-title">
|
||||
{Translate.genshin} - {Translate.gacha}
|
||||
</div>
|
||||
|
||||
{loading && <LoadingSpinner />}
|
||||
|
||||
<PillTabs
|
||||
items={(gachaList ?? []).map((item) => ({
|
||||
key: String(item.gacha_id),
|
||||
label: `${item.gacha_name}(${item.begin_time} - ${item.end_time})`,
|
||||
children: <GachaPool gachaId={item.gacha_id} />,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
GenshinPanel.displayName = 'GenshinPanel';
|
||||
|
||||
export default GenshinPanel;
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { AppGachaItem } from 'genshin-gacha-kit';
|
||||
import { wishVideoUrl } from './consts';
|
||||
|
||||
export function getAppGachaItemText(item: AppGachaItem) {
|
||||
if (item.count >= 2) {
|
||||
return `${item.name}(${item.count})`;
|
||||
} else {
|
||||
return item.name;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseResultType(
|
||||
items: AppGachaItem[]
|
||||
): keyof typeof wishVideoUrl {
|
||||
if (items.length === 1) {
|
||||
// single
|
||||
const rarity = items[0].rarity;
|
||||
if (rarity === 3) {
|
||||
return '3star-single';
|
||||
} else if (rarity === 4) {
|
||||
return '4star-single';
|
||||
} else if (rarity === 5) {
|
||||
return '5star-single';
|
||||
}
|
||||
} else {
|
||||
if (items.some((i) => i.rarity === 5)) {
|
||||
return '5star';
|
||||
} else if (items.some((i) => i.rarity === 4)) {
|
||||
return '4star';
|
||||
}
|
||||
}
|
||||
|
||||
return '3star-single';
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
.img-color(@color, @top: 500px) {
|
||||
position: absolute;
|
||||
top: @top;
|
||||
left: 0;
|
||||
filter: drop-shadow(0 -@top 0 @color);
|
||||
}
|
||||
|
||||
#tailchat-app {
|
||||
--plugin-genshinloadingbar-background-color: #f5f5f5;
|
||||
--plugin-genshinloadingbar-prospect-color: #666666;
|
||||
|
||||
@loading-img-height: 62.5px;
|
||||
@loading-img-width: 500px;
|
||||
@mobile: 719px;
|
||||
@animation: plugin-genshin-loading-bar 3.5s cubic-bezier(0.28, 0.11, 0.32, 1) infinite
|
||||
forwards;
|
||||
|
||||
.dark {
|
||||
--plugin-genshinloadingbar-background-color: #2c2b30;
|
||||
--plugin-genshinloadingbar-prospect-color: #ece5d8;
|
||||
}
|
||||
|
||||
.plugin-genshin-loading-bar {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: @loading-img-width;
|
||||
height: @loading-img-height;
|
||||
transform: translate(-50%, -50%) scale(0.8);
|
||||
transition: all 0.5s;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
.img-color(var(--plugin-genshinloadingbar-background-color));
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
.img-color(var(--plugin-genshinloadingbar-prospect-color));
|
||||
width: @loading-img-width;
|
||||
height: @loading-img-height;
|
||||
background: url('https://yuanshen.site/imgs/loading-bar.png') no-repeat
|
||||
left 100%;
|
||||
background-size: @loading-img-width @loading-img-height;
|
||||
background-position-x: 0;
|
||||
animation: @animation;
|
||||
}
|
||||
|
||||
@media screen and (max-width: @mobile) {
|
||||
// Hide when vertical screen
|
||||
& {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Horizontal when screen display
|
||||
@media screen and (orientation: landscape) {
|
||||
& {
|
||||
display: block !important;
|
||||
transform: translate(-50%, -50%) scale(0.7) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@supports not (filter: drop-shadow(0 0 0 #fff)) {
|
||||
// If the browser does not support Filter
|
||||
&:before {
|
||||
content: 'Your browser does not support the animation';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes plugin-genshin-loading-bar {
|
||||
0% {
|
||||
width: 0;
|
||||
background-size: @loading-img-width @loading-img-height;
|
||||
}
|
||||
|
||||
16.6% {
|
||||
}
|
||||
|
||||
33.2% {
|
||||
}
|
||||
|
||||
49.8% {
|
||||
}
|
||||
|
||||
66.4% {
|
||||
}
|
||||
|
||||
83% {
|
||||
}
|
||||
|
||||
100% {
|
||||
width: @loading-img-width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import './GenshinLoading.less';
|
||||
|
||||
export const GenshinLoading: React.FC = React.memo(() => {
|
||||
return (
|
||||
<div
|
||||
className="plugin-genshin-loading-bar"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img src="https://yuanshen.site/imgs/loading-bar.png" alt="Loading..." />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
GenshinLoading.displayName = 'GenshinLoading';
|
||||
@@ -0,0 +1,20 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import parser from 'html-react-parser';
|
||||
|
||||
/**
|
||||
* 原神富文本渲染
|
||||
*/
|
||||
export const GenshinRichtext: React.FC<{
|
||||
raw: string;
|
||||
}> = React.memo(({ raw }) => {
|
||||
const el = useMemo(() => {
|
||||
const processedHtml = raw.replace(
|
||||
/\<color=(.*?)\>(.*?)\<\/color\>/g,
|
||||
'<span style="color: $1;">$2</span>'
|
||||
);
|
||||
|
||||
return parser(processedHtml);
|
||||
}, [raw]);
|
||||
return <>{el}</>;
|
||||
});
|
||||
GenshinRichtext.displayName = 'GenshinRichtext';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { GenshinRichtext } from '../GenshinRichtext';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
describe('GenshinRichtext', () => {
|
||||
const testRawRichtext = `「<color=#cc9046FF>陵薮</color>市朝」活动祈愿已开启。活动期间内,<color=#c93f23>限定</color>5星角色<color=#debd6c>「尘世闲游·钟离(岩)」</color>以及4星角色<color=#00BFFF>「少年春衫薄·行秋(水)」</color>、<color=#945dc4>「无冕的龙王·北斗(雷)」</color>、<color=#EC4923>「智明无邪·烟绯(火)」</color>的祈愿获取概率将<color=#c93f23>大幅提升</color>!</P>
|
||||
<color=#c93f23>※以上角色中,限定角色不会进入「奔行世间」常驻祈愿。 </color></P>
|
||||
<br />
|
||||
※一般情况下所有角色或武器均适用基础概率,如触发概率UP、保底等以具体规则为准。 </P>
|
||||
<br />
|
||||
〓祈愿规则〓</P>
|
||||
【5星物品】</P>
|
||||
在本期「<color=#cc9046FF>陵薮</color>市朝」活动祈愿中,5星角色祈愿的基础概率为<color=#c93f23>0.600%</color>,综合概率(含保底)为<color=#c93f23>1.600%</color>,最多<color=#c93f23>90</color>次祈愿必定能通过保底获取5星角色。</P>
|
||||
当祈愿获取到5星角色时,有<color=#c93f23>50.000%</color>的概率为本期5星UP角色<color=#debd6c>「尘世闲游·钟离(岩)」</color>。如果本次祈愿获取的5星角色非本期5星UP角色,下次祈愿获取的5星角色<color=#c93f23>必定</color>为本期5星UP角色。</P>
|
||||
【4星物品】</P>
|
||||
在本期「<color=#cc9046FF>陵薮</color>市朝」活动祈愿中,4星物品祈愿的基础概率为<color=#c93f23>5.100%</color>,4星角色祈愿的基础概率为<color=#c93f23>2.550%</color>,4星武器祈愿的基础概率为<color=#c93f23>2.550%</color>,4星物品祈愿的综合概率(含保底)为<color=#c93f23>13.000%</color>。最多<color=#c93f23>10</color>次祈愿必定能通过保底获取4星或以上物品,通过保底获取4星物品的概率为<color=#c93f23>99.400%</color>,获取5星物品的概率为<color=#c93f23>0.600%</color>。</P>
|
||||
当祈愿获取到4星物品时,有<color=#c93f23>50.000%</color>的概率为本期4星UP角色<color=#00BFFF>「少年春衫薄·行秋(水)」</color>、<color=#945dc4>「无冕的龙王·北斗(雷)」</color>、<color=#EC4923>「智明无邪·烟绯(火)」</color>中的一个。如果本次祈愿获取的4星物品非本期4星UP角色,下次祈愿获取的4星物品<color=#c93f23>必定</color>为本期4星UP角色。当祈愿获取到4星UP物品时,每个本期4星UP角色的获取概率均等。</P>
|
||||
<br />
|
||||
获得4星武器时,会同时获得2个<color=#bd6932>无主的星辉</color>作为副产物;获得3星武器时,会同时获得15个<color=#a256e1>无主的星尘</color>作为副产物。</P>
|
||||
<br />
|
||||
〓若获得重复角色〓</P>
|
||||
无论通过何种方式(包含但不限于祈愿、商城兑换、系统赠送等)第2~7次获得相同5星角色时,每次将转化为1个<color=#a256e1>对应角色的命星</color>和10个<color=#bd6932>无主的星辉</color>;第8次及之后获得,将仅转化为25个<color=#bd6932>无主的星辉</color>。</P>
|
||||
无论通过何种方式(包含但不限于祈愿、商城兑换、系统赠送等)第2~7次获得相同4星角色时,每次将转化为1个<color=#a256e1>对应角色的命星</color>和2个<color=#bd6932>无主的星辉</color>;第8次及之后获得,将仅转化为5个<color=#bd6932>无主的星辉</color>。</P>
|
||||
<br />
|
||||
※本祈愿属于「角色活动祈愿」,「角色活动祈愿」和「角色活动祈愿-2」的祈愿次数保底完全共享,会一直共同累计在「角色活动祈愿」和「角色活动祈愿-2」中,与其他祈愿的祈愿次数保底相互独立计算,互不影响。</P>`;
|
||||
|
||||
test('test', () => {
|
||||
const wrapper = render(<GenshinRichtext raw={testRawRichtext} />);
|
||||
expect(wrapper.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,280 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GenshinRichtext test 1`] = `
|
||||
<div>
|
||||
「
|
||||
<span
|
||||
style="color: rgba(204, 144, 70, 1);"
|
||||
>
|
||||
陵薮
|
||||
</span>
|
||||
市朝」活动祈愿已开启。活动期间内,
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
限定
|
||||
</span>
|
||||
5星角色
|
||||
<span
|
||||
style="color: rgb(222, 189, 108);"
|
||||
>
|
||||
「尘世闲游·钟离(岩)」
|
||||
</span>
|
||||
以及4星角色
|
||||
<span
|
||||
style="color: rgb(0, 191, 255);"
|
||||
>
|
||||
「少年春衫薄·行秋(水)」
|
||||
</span>
|
||||
、
|
||||
<span
|
||||
style="color: rgb(148, 93, 196);"
|
||||
>
|
||||
「无冕的龙王·北斗(雷)」
|
||||
</span>
|
||||
、
|
||||
<span
|
||||
style="color: rgb(236, 73, 35);"
|
||||
>
|
||||
「智明无邪·烟绯(火)」
|
||||
</span>
|
||||
的祈愿获取概率将
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
大幅提升
|
||||
</span>
|
||||
!
|
||||
<p />
|
||||
|
||||
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
※以上角色中,限定角色不会进入「奔行世间」常驻祈愿。
|
||||
</span>
|
||||
<p />
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
※一般情况下所有角色或武器均适用基础概率,如触发概率UP、保底等以具体规则为准。
|
||||
<p />
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
〓祈愿规则〓
|
||||
<p />
|
||||
|
||||
【5星物品】
|
||||
<p />
|
||||
|
||||
在本期「
|
||||
<span
|
||||
style="color: rgba(204, 144, 70, 1);"
|
||||
>
|
||||
陵薮
|
||||
</span>
|
||||
市朝」活动祈愿中,5星角色祈愿的基础概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
0.600%
|
||||
</span>
|
||||
,综合概率(含保底)为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
1.600%
|
||||
</span>
|
||||
,最多
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
90
|
||||
</span>
|
||||
次祈愿必定能通过保底获取5星角色。
|
||||
<p />
|
||||
|
||||
当祈愿获取到5星角色时,有
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
50.000%
|
||||
</span>
|
||||
的概率为本期5星UP角色
|
||||
<span
|
||||
style="color: rgb(222, 189, 108);"
|
||||
>
|
||||
「尘世闲游·钟离(岩)」
|
||||
</span>
|
||||
。如果本次祈愿获取的5星角色非本期5星UP角色,下次祈愿获取的5星角色
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
必定
|
||||
</span>
|
||||
为本期5星UP角色。
|
||||
<p />
|
||||
|
||||
【4星物品】
|
||||
<p />
|
||||
|
||||
在本期「
|
||||
<span
|
||||
style="color: rgba(204, 144, 70, 1);"
|
||||
>
|
||||
陵薮
|
||||
</span>
|
||||
市朝」活动祈愿中,4星物品祈愿的基础概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
5.100%
|
||||
</span>
|
||||
,4星角色祈愿的基础概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
2.550%
|
||||
</span>
|
||||
,4星武器祈愿的基础概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
2.550%
|
||||
</span>
|
||||
,4星物品祈愿的综合概率(含保底)为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
13.000%
|
||||
</span>
|
||||
。最多
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
10
|
||||
</span>
|
||||
次祈愿必定能通过保底获取4星或以上物品,通过保底获取4星物品的概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
99.400%
|
||||
</span>
|
||||
,获取5星物品的概率为
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
0.600%
|
||||
</span>
|
||||
。
|
||||
<p />
|
||||
|
||||
当祈愿获取到4星物品时,有
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
50.000%
|
||||
</span>
|
||||
的概率为本期4星UP角色
|
||||
<span
|
||||
style="color: rgb(0, 191, 255);"
|
||||
>
|
||||
「少年春衫薄·行秋(水)」
|
||||
</span>
|
||||
、
|
||||
<span
|
||||
style="color: rgb(148, 93, 196);"
|
||||
>
|
||||
「无冕的龙王·北斗(雷)」
|
||||
</span>
|
||||
、
|
||||
<span
|
||||
style="color: rgb(236, 73, 35);"
|
||||
>
|
||||
「智明无邪·烟绯(火)」
|
||||
</span>
|
||||
中的一个。如果本次祈愿获取的4星物品非本期4星UP角色,下次祈愿获取的4星物品
|
||||
<span
|
||||
style="color: rgb(201, 63, 35);"
|
||||
>
|
||||
必定
|
||||
</span>
|
||||
为本期4星UP角色。当祈愿获取到4星UP物品时,每个本期4星UP角色的获取概率均等。
|
||||
<p />
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
获得4星武器时,会同时获得2个
|
||||
<span
|
||||
style="color: rgb(189, 105, 50);"
|
||||
>
|
||||
无主的星辉
|
||||
</span>
|
||||
作为副产物;获得3星武器时,会同时获得15个
|
||||
<span
|
||||
style="color: rgb(162, 86, 225);"
|
||||
>
|
||||
无主的星尘
|
||||
</span>
|
||||
作为副产物。
|
||||
<p />
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
〓若获得重复角色〓
|
||||
<p />
|
||||
|
||||
无论通过何种方式(包含但不限于祈愿、商城兑换、系统赠送等)第2~7次获得相同5星角色时,每次将转化为1个
|
||||
<span
|
||||
style="color: rgb(162, 86, 225);"
|
||||
>
|
||||
对应角色的命星
|
||||
</span>
|
||||
和10个
|
||||
<span
|
||||
style="color: rgb(189, 105, 50);"
|
||||
>
|
||||
无主的星辉
|
||||
</span>
|
||||
;第8次及之后获得,将仅转化为25个
|
||||
<span
|
||||
style="color: rgb(189, 105, 50);"
|
||||
>
|
||||
无主的星辉
|
||||
</span>
|
||||
。
|
||||
<p />
|
||||
|
||||
无论通过何种方式(包含但不限于祈愿、商城兑换、系统赠送等)第2~7次获得相同4星角色时,每次将转化为1个
|
||||
<span
|
||||
style="color: rgb(162, 86, 225);"
|
||||
>
|
||||
对应角色的命星
|
||||
</span>
|
||||
和2个
|
||||
<span
|
||||
style="color: rgb(189, 105, 50);"
|
||||
>
|
||||
无主的星辉
|
||||
</span>
|
||||
;第8次及之后获得,将仅转化为5个
|
||||
<span
|
||||
style="color: rgb(189, 105, 50);"
|
||||
>
|
||||
无主的星辉
|
||||
</span>
|
||||
。
|
||||
<p />
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
※本祈愿属于「角色活动祈愿」,「角色活动祈愿」和「角色活动祈愿-2」的祈愿次数保底完全共享,会一直共同累计在「角色活动祈愿」和「角色活动祈愿-2」中,与其他祈愿的祈愿次数保底相互独立计算,互不影响。
|
||||
<p />
|
||||
</div>
|
||||
`;
|
||||
10
client/web/plugins/com.msgbyte.genshin/src/index.tsx
Normal file
10
client/web/plugins/com.msgbyte.genshin/src/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { regCustomPanel, Loadable } from '@capital/common';
|
||||
import { Translate } from './translate';
|
||||
|
||||
regCustomPanel({
|
||||
position: 'personal',
|
||||
icon: 'akar-icons:game-controller',
|
||||
name: 'com.msgbyte.genshin/genshinPanel',
|
||||
label: Translate.genshin,
|
||||
render: Loadable(() => import('./GenshinPanel')),
|
||||
});
|
||||
6
client/web/plugins/com.msgbyte.genshin/src/translate.ts
Normal file
6
client/web/plugins/com.msgbyte.genshin/src/translate.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { localTrans } from '@capital/common';
|
||||
|
||||
export const Translate = {
|
||||
genshin: localTrans({ 'zh-CN': '原神', 'en-US': 'Genshin' }),
|
||||
gacha: localTrans({ 'zh-CN': '抽卡', 'en-US': 'Gacha' }),
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 打开一个全屏的video
|
||||
*/
|
||||
export async function openFullScreenVideo(src: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const containerEl = document.createElement('div');
|
||||
containerEl.style.position = 'fixed';
|
||||
containerEl.style.height = '100vh';
|
||||
containerEl.style.width = '100vw';
|
||||
containerEl.style.left = '0px';
|
||||
containerEl.style.top = '0px';
|
||||
containerEl.style.zIndex = '99999';
|
||||
containerEl.style.backgroundColor = '#000000';
|
||||
containerEl.style.display = 'flex';
|
||||
containerEl.style.alignItems = 'center';
|
||||
containerEl.style.justifyContent = 'center';
|
||||
|
||||
const videoEl = document.createElement('video');
|
||||
videoEl.src = src;
|
||||
|
||||
videoEl.autoplay = true;
|
||||
videoEl.addEventListener('ended', () => {
|
||||
containerEl.removeChild(videoEl);
|
||||
document.body.removeChild(containerEl);
|
||||
resolve();
|
||||
});
|
||||
videoEl.addEventListener('error', () => {
|
||||
reject();
|
||||
});
|
||||
|
||||
containerEl.appendChild(videoEl);
|
||||
document.body.appendChild(containerEl);
|
||||
});
|
||||
}
|
||||
9
client/web/plugins/com.msgbyte.genshin/tsconfig.json
Normal file
9
client/web/plugins/com.msgbyte.genshin/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"@capital/*": ["../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user