fear: 统计界面框架

This commit is contained in:
unanmed 2025-05-25 16:58:39 +08:00
parent 5cd00534c0
commit 86bc383d65
2 changed files with 389 additions and 0 deletions

View File

@ -0,0 +1,191 @@
import { DefaultProps } from '@motajs/render-vue';
import Scroll from 'packages/legacy-ui/src/components/scroll.vue';
import { computed, defineComponent, ref, SlotsType, VNode } from 'vue';
import { Selection } from './misc';
import { ElementLocator } from '@motajs/render-core';
import { Font } from '@motajs/render-style';
import { SetupComponentOptions } from '@motajs/system-ui';
export interface ListProps extends DefaultProps {
/** 列表内容,第一项表示 id第二项表示显示的内容 */
list: [string, string][];
/** 当前选中的项 */
selected: string;
/** 定位 */
loc: ElementLocator;
/** 每行的高度,默认 18 */
lineHeight?: number;
/** 字体 */
font?: Font;
/** 使用 winskin 作为光标 */
winskin?: ImageIds;
/** 使用指定样式作为光标背景 */
color?: CanvasStyle;
/** 使用指定样式作为光标边框 */
border?: CanvasStyle;
/** 选择图标的不透明度范围 */
alphaRange?: [number, number];
}
export type ListEmits = {
/**
*
* @param key id
*/
update: (key: string) => void;
'update:selected': (value: string) => void;
};
const listProps = {
props: [
'list',
'selected',
'loc',
'lineHeight',
'font',
'winskin',
'color',
'border',
'alphaRange'
],
emits: ['update', 'update:selected']
} satisfies SetupComponentOptions<ListProps, ListEmits, keyof ListEmits>;
export const List = defineComponent<ListProps, ListEmits, keyof ListEmits>(
(props, { emit }) => {
const selected = ref(props.list[0][0]);
const lineHeight = computed(() => props.lineHeight ?? 18);
const select = (value: string) => {
selected.value = value;
emit('update', value);
emit('update:selected', value);
};
return () => (
<Scroll>
{props.list.map((v, i) => {
const [key, value] = v;
const loc: ElementLocator = [
0,
lineHeight.value * i,
props.loc[2] ?? 200,
lineHeight.value
];
return (
<container onClick={() => select(key)}>
{selected.value === key && (
<Selection
loc={loc}
color={props.color}
border={props.border}
winskin={props.winskin}
alphaRange={props.alphaRange}
/>
)}
<text text={value} font={props.font} />
</container>
);
})}
</Scroll>
);
},
listProps
);
export interface ListPageProps extends ListProps {
/** 组件定位 */
loc: ElementLocator;
/** 列表所占比例 */
basis?: number;
/** 列表是否排列在右侧 */
right?: boolean;
/** 是否显示关闭按钮 */
close?: boolean;
}
export type ListPageEmits = {
close: () => void;
} & ListEmits;
export type ListPageSlots = SlotsType<{
default: (key: string) => VNode | VNode[];
[x: string]: (key: string) => VNode | VNode[];
}>;
const listPageProps = {
props: [
'basis',
'right',
'list',
'selected',
'loc',
'lineHeight',
'font',
'winskin',
'color',
'border',
'alphaRange'
],
emits: ['update', 'update:selected', 'close']
} satisfies SetupComponentOptions<
ListPageProps,
ListPageEmits,
keyof ListPageEmits,
ListPageSlots
>;
export const ListPage = defineComponent<
ListPageProps,
ListPageEmits,
keyof ListPageEmits,
ListPageSlots
>((props, { emit, slots }) => {
const selected = ref(props.selected);
const basis = computed(() => props.basis ?? 0.3);
const width = computed(() => props.loc[2] ?? 200);
const height = computed(() => props.loc[3] ?? 200);
const listLoc = computed<ElementLocator>(() => {
const listWidth = width.value * basis.value;
if (props.right) {
return [width.value - listWidth, 0, listWidth, height.value];
} else {
return [0, 0, listWidth, height.value];
}
});
const contentLoc = computed<ElementLocator>(() => {
const contentWidth = width.value * (1 - basis.value);
if (props.right) {
return [0, 0, contentWidth, height.value];
} else {
return [width.value - contentWidth, 0, contentWidth, height.value];
}
});
const update = (key: string) => {
emit('update', key);
emit('update:selected', key);
};
return () => (
<container>
<List
{...props}
loc={listLoc.value}
list={props.list}
v-model:selected={selected.value}
onUpdate={update}
></List>
<container loc={contentLoc.value}>
{slots[selected.value]?.(selected.value) ??
slots.default?.(selected.value)}
</container>
{props.close && (
<text text="关闭" cursor="pointer" font={props.font}></text>
)}
</container>
);
}, listPageProps);

View File

@ -0,0 +1,198 @@
import {
GameUI,
IUIMountable,
SetupComponentOptions,
UIComponentProps
} from '@motajs/system-ui';
import { defineComponent } from 'vue';
import { ListPage } from '../components/list';
import { waitbox } from '../components';
import { DefaultProps } from '@motajs/render-vue';
import { ItemState } from '@user/data-state';
interface StatisticsDataOneFloor {
enemyCount: number;
potionCount: number;
gemCount: number;
potionValue: number;
atkValue: number;
defValue: number;
mdefValue: number;
}
interface StatisticsData {
total: StatisticsDataOneFloor;
floors: Map<FloorIds, StatisticsDataOneFloor>;
}
export interface StatisticsProps extends UIComponentProps, DefaultProps {
data: StatisticsData;
}
const statisticsProps = {
props: ['data']
} satisfies SetupComponentOptions<StatisticsProps>;
export const Statistics = defineComponent<StatisticsProps>(props => {
const list: [string, string][] = [
['total', '总览'],
['floor', '楼层'],
['enemy', '怪物'],
['potion', '血瓶宝石']
];
const close = () => {
props.controller.close(props.instance);
};
return () => (
<ListPage
list={list}
selected="total"
loc={[180, 0, 480, 480]}
close
onClose={close}
>
{{
total: () => <TotalStatistics data={props.data} />,
floor: () => <FloorStatistics data={props.data} />,
enemy: () => <EnemyStatistics data={props.data} />,
potion: () => <PotionStatistics data={props.data} />
}}
</ListPage>
);
}, statisticsProps);
interface StatisticsPanelProps extends DefaultProps {
data: StatisticsData;
}
const TotalStatistics = defineComponent<StatisticsPanelProps>(props => {
return () => <container></container>;
}, statisticsProps);
const FloorStatistics = defineComponent<StatisticsPanelProps>(props => {
return () => <container></container>;
}, statisticsProps);
const EnemyStatistics = defineComponent<StatisticsPanelProps>(props => {
return () => <container></container>;
}, statisticsProps);
const PotionStatistics = defineComponent<StatisticsPanelProps>(props => {
return () => <container></container>;
}, statisticsProps);
function calculateStatistics(): StatisticsData {
core.setFlag('__statistics__', true);
const hero = core.status.hero;
const diff: Record<string | symbol, number> = {};
const handler: ProxyHandler<HeroStatus> = {
set(_target, p, newValue) {
if (typeof newValue === 'number') {
diff[p] ??= 0;
diff[p] += newValue;
}
return true;
}
};
const proxy = new Proxy(hero, handler);
core.status.hero = proxy;
const floors = new Map<FloorIds, StatisticsDataOneFloor>();
core.floorIds.forEach(v => {
core.extractBlocks(v);
const statistics: StatisticsDataOneFloor = {
enemyCount: 0,
potionCount: 0,
gemCount: 0,
potionValue: 0,
atkValue: 0,
defValue: 0,
mdefValue: 0
};
core.status.maps[v].blocks.forEach(v => {
if (v.event.cls === 'enemys' || v.event.cls === 'enemy48') {
statistics.enemyCount++;
} else if (v.event.cls === 'items') {
const item = ItemState.items.get(
v.event.id as AllIdsOf<'items'>
);
if (!item) return;
if (item.cls === 'items') {
try {
item.itemEffectFn?.();
} catch {
// pass
}
if (diff.hp > 0) {
statistics.potionCount++;
statistics.potionValue += diff.hp;
}
if (diff.atk > 0 || diff.def > 0 || diff.mdef > 0) {
statistics.gemCount++;
}
if (diff.atk > 0) {
statistics.atkValue += diff.atk;
}
if (diff.def > 0) {
statistics.defValue += diff.def;
}
if (diff.mdef > 0) {
statistics.mdefValue += diff.mdef;
}
}
}
for (const key of Object.keys(diff)) {
diff[key] = 0;
}
});
floors.set(v, statistics);
});
core.status.hero = hero;
window.hero = hero;
window.flags = core.status.hero.flags;
core.removeFlag('__statistics__');
const total = floors.values().reduce((prev, curr) => {
prev.atkValue += curr.atkValue;
prev.defValue += curr.defValue;
prev.enemyCount += curr.enemyCount;
prev.gemCount += curr.gemCount;
prev.mdefValue += curr.mdefValue;
prev.potionCount += curr.potionCount;
prev.potionValue += curr.potionValue;
return prev;
});
return {
total,
floors
};
}
/**
*
* @param controller UI
*/
export async function openStatistics(controller: IUIMountable) {
const cal = Promise.resolve().then<StatisticsData>(() => {
return new Promise(res => {
const data = calculateStatistics();
res(data);
});
});
const data = await waitbox(
controller,
[240 + 180, void 0, void 0, 240, 0.5, 0.5],
240,
cal,
{
text: '正在统计...'
}
);
controller.open(StatisticsUI, { data: data });
}
export const StatisticsUI = new GameUI('statistics', Statistics);