mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-18 17:48:52 +08:00
404 lines
14 KiB
TypeScript
404 lines
14 KiB
TypeScript
import { GameUI } from '@/core/system';
|
|
import { computed, defineComponent, ref, watch } from 'vue';
|
|
import { SetupComponentOptions, TextContent } from '../components';
|
|
import { ElementLocator, Sprite } from '@/core/render';
|
|
import { transitionedColor } from '../use';
|
|
import { linear } from 'mutate-animate';
|
|
import { Scroll } from '../components/scroll';
|
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
|
import { getArea, MinimapDrawer } from '@/plugin/ui/fly';
|
|
import {
|
|
NumpadToolbar,
|
|
PlayingToolbar,
|
|
ReplayingStatus,
|
|
ReplayingToolbar
|
|
} from './toolbar';
|
|
|
|
export interface ILeftHeroStatus {
|
|
hp: number;
|
|
atk: number;
|
|
def: number;
|
|
mdef: number;
|
|
money: number;
|
|
exp: number;
|
|
yellowKey: number;
|
|
blueKey: number;
|
|
redKey: number;
|
|
floor: FloorIds;
|
|
lv: string;
|
|
/** 生命回复 */
|
|
regen: number;
|
|
/** 额外攻击 */
|
|
exAtk: number;
|
|
/** 魔法防御 */
|
|
magicDef: number;
|
|
}
|
|
|
|
interface StatusBarProps<T> {
|
|
loc: ElementLocator;
|
|
status: T;
|
|
}
|
|
|
|
const statusBarProps = {
|
|
props: ['loc', 'status']
|
|
} satisfies SetupComponentOptions<StatusBarProps<unknown>>;
|
|
|
|
export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
|
p => {
|
|
const hpIcon = core.material.images.images['hp.png'];
|
|
const atkIcon = core.material.images.images['atk.png'];
|
|
const defIcon = core.material.images.images['def.png'];
|
|
const mdefIcon = core.material.images.images['IQ.png'];
|
|
const moneyIcon = core.material.images.images['money.png'];
|
|
const expIcon = core.material.images.images['exp.png'];
|
|
|
|
const s = p.status;
|
|
const f = core.formatBigNumber;
|
|
|
|
const floorName = computed(() => core.floors[s.floor]?.title ?? '');
|
|
|
|
const key = (num: number) => {
|
|
return num.toString().padStart(2, '0');
|
|
};
|
|
|
|
const font1 = '18px normal';
|
|
const font2 = 'bold 18px normal';
|
|
const font3 = 'bold 14px normal';
|
|
|
|
const iconLoc = (n: number): ElementLocator => {
|
|
return [16, 76 + 44 * n, 32, 32];
|
|
};
|
|
|
|
const textLoc = (n: number): ElementLocator => {
|
|
return [60, 92 + 44 * n, void 0, void 0, 0, 0.5];
|
|
};
|
|
|
|
const central = (y: number): ElementLocator => {
|
|
const width = p.loc[2] ?? 200;
|
|
return [width / 2, y, void 0, void 0, 0.5, 0.5];
|
|
};
|
|
|
|
const right = (y: number): ElementLocator => {
|
|
const width = p.loc[2] ?? 200;
|
|
return [width - 16, y, void 0, void 0, 1, 0.5];
|
|
};
|
|
|
|
const keyCount = 3;
|
|
const keyY = 92 + 44 * 6;
|
|
const keyLoc = (n: number): ElementLocator => {
|
|
const width = p.loc[2] ?? 200;
|
|
const per = width / (keyCount + 1);
|
|
return [per * (n + 1), keyY, void 0, void 0, 0.5, 0.5];
|
|
};
|
|
|
|
return () => {
|
|
return (
|
|
<container loc={p.loc}>
|
|
<text
|
|
text={floorName.value}
|
|
loc={central(24)}
|
|
font={font1}
|
|
cursor="pointer"
|
|
></text>
|
|
<text text={s.lv} loc={central(54)} font={font1}></text>
|
|
<image image={hpIcon} loc={iconLoc(0)}></image>
|
|
<text text={f(s.hp)} loc={textLoc(0)} font={font1}></text>
|
|
<text
|
|
text={`+${f(s.regen)}/t`}
|
|
loc={right(110)}
|
|
font={font3}
|
|
fillStyle="#a7ffa7"
|
|
></text>
|
|
<image image={atkIcon} loc={iconLoc(1)}></image>
|
|
<text text={f(s.atk)} loc={textLoc(1)} font={font1}></text>
|
|
<text
|
|
text={`+${f(s.exAtk)}`}
|
|
loc={right(154)}
|
|
font={font3}
|
|
fillStyle="#ffd3d3"
|
|
></text>
|
|
<image image={defIcon} loc={iconLoc(2)}></image>
|
|
<text text={f(s.def)} loc={textLoc(2)} font={font1}></text>
|
|
{s.magicDef > 0 && (
|
|
<text
|
|
text={`+${f(s.magicDef)}`}
|
|
loc={right(198)}
|
|
font={font3}
|
|
fillStyle="#b0bdff"
|
|
></text>
|
|
)}
|
|
<image image={mdefIcon} loc={iconLoc(3)}></image>
|
|
<text text={f(s.mdef)} loc={textLoc(3)} font={font1}></text>
|
|
<image image={moneyIcon} loc={iconLoc(4)}></image>
|
|
<text text={f(s.money)} loc={textLoc(4)} font={font1} />
|
|
<image image={expIcon} loc={iconLoc(5)}></image>
|
|
<text text={f(s.exp)} loc={textLoc(5)} font={font1}></text>
|
|
<text
|
|
text={key(s.yellowKey)}
|
|
loc={keyLoc(0)}
|
|
font={font2}
|
|
fillStyle="#fca"
|
|
></text>
|
|
<text
|
|
text={key(s.blueKey)}
|
|
loc={keyLoc(1)}
|
|
font={font2}
|
|
fillStyle="#aad"
|
|
></text>
|
|
<text
|
|
text={key(s.redKey)}
|
|
loc={keyLoc(2)}
|
|
font={font2}
|
|
fillStyle="#f88"
|
|
></text>
|
|
<text
|
|
text="技能树"
|
|
loc={central(396)}
|
|
font={font1}
|
|
cursor="pointer"
|
|
></text>
|
|
<text
|
|
text="查看技能"
|
|
loc={central(428)}
|
|
font={font1}
|
|
cursor="pointer"
|
|
></text>
|
|
</container>
|
|
);
|
|
};
|
|
},
|
|
statusBarProps
|
|
);
|
|
|
|
interface RightStatusBarMisc {
|
|
name: string;
|
|
value: string;
|
|
nameColor: string;
|
|
valueColor: string;
|
|
}
|
|
|
|
export interface IRightHeroStatus {
|
|
/** 自动切换技能 */
|
|
autoSkill: boolean;
|
|
/** 当前开启的技能 */
|
|
skillName: string;
|
|
/** 技能描述 */
|
|
skillDesc: string;
|
|
/** 跳跃剩余次数,-1 表示未开启,-2表示当前楼层不能跳 */
|
|
jumpCount: number;
|
|
/** 治愈之泉剩余次数,-1 表示未开启 */
|
|
springCount: number;
|
|
/** 当前楼层 */
|
|
floor: FloorIds;
|
|
/** 是否正在录像播放 */
|
|
replaying: boolean;
|
|
/** 录像播放状态 */
|
|
replayStatus: ReplayingStatus;
|
|
}
|
|
|
|
export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
|
|
p => {
|
|
const font1 = '18px normal';
|
|
const font2 = '16px normal';
|
|
|
|
const minimap = ref<Sprite>();
|
|
const inNumpad = ref(false);
|
|
|
|
const onNumpad = () => {
|
|
inNumpad.value = !inNumpad.value;
|
|
};
|
|
|
|
const s = p.status;
|
|
const skill = computed(() =>
|
|
s.autoSkill ? '已开启自动切换' : s.skillName
|
|
);
|
|
const skillDesc = computed(() =>
|
|
s.autoSkill ? '自动切换技能时,会自动选择最优技能' : s.skillDesc
|
|
);
|
|
|
|
const skillColor = transitionedColor('#284', 200, linear())!;
|
|
|
|
watch(
|
|
() => s.autoSkill,
|
|
value => {
|
|
skillColor.set(value ? '#284' : '#824');
|
|
}
|
|
);
|
|
|
|
const miscData = computed<RightStatusBarMisc[]>(() => {
|
|
const data: RightStatusBarMisc[] = [];
|
|
if (s.jumpCount !== -1) {
|
|
const text =
|
|
s.jumpCount === -2 ? '不可跳跃' : s.jumpCount.toString();
|
|
data.push({
|
|
name: '跳跃剩余',
|
|
nameColor: '#fff',
|
|
value: text,
|
|
valueColor: '#fff'
|
|
});
|
|
}
|
|
if (s.springCount >= 0) {
|
|
data.push({
|
|
name: '治愈之泉',
|
|
nameColor: '#a7ffa7',
|
|
value: s.springCount.toString(),
|
|
valueColor: '#a7ffa7'
|
|
});
|
|
}
|
|
return data;
|
|
});
|
|
|
|
const central = (y: number): ElementLocator => {
|
|
const width = p.loc[2] ?? 200;
|
|
return [width / 2, y, void 0, void 0, 0.5, 0.5];
|
|
};
|
|
|
|
const middle = (x: number, y: number): ElementLocator => {
|
|
return [x, y, void 0, void 0, 0, 0.5];
|
|
};
|
|
|
|
const changeAutoSkill = () => {
|
|
const { HeroSkill } = Mota.require('module', 'Mechanism');
|
|
const auto = !s.autoSkill;
|
|
HeroSkill.setAutoSkill(auto);
|
|
core.status.route.push(`set:autoSkill:${auto}`);
|
|
core.updateStatusBar();
|
|
};
|
|
|
|
const area = getArea();
|
|
const minimapDrawer = new MinimapDrawer(
|
|
document.createElement('canvas')
|
|
);
|
|
minimapDrawer.noBorder = true;
|
|
minimapDrawer.scale = 4;
|
|
minimapDrawer.showInfo = true;
|
|
let linked = false;
|
|
const drawMinimap = (canvas: MotaOffscreenCanvas2D) => {
|
|
const ctx = canvas.ctx;
|
|
ctx.save();
|
|
ctx.scale(
|
|
1 / core.domStyle.scale / devicePixelRatio,
|
|
1 / core.domStyle.scale / devicePixelRatio
|
|
);
|
|
if (!linked) {
|
|
minimapDrawer.link(canvas.canvas);
|
|
linked = true;
|
|
}
|
|
if (minimapDrawer.nowFloor !== s.floor) {
|
|
minimapDrawer.drawedThumbnail = {};
|
|
} else {
|
|
minimapDrawer.drawToTarget();
|
|
ctx.restore();
|
|
return;
|
|
}
|
|
minimapDrawer.nowFloor = s.floor;
|
|
minimapDrawer.nowArea =
|
|
Object.keys(area).find(v =>
|
|
area[v].includes(core.status.floorId)
|
|
) ?? '';
|
|
minimapDrawer.locateMap(minimapDrawer.nowFloor);
|
|
minimapDrawer.drawMap();
|
|
ctx.restore();
|
|
};
|
|
|
|
watch(
|
|
() => s.floor,
|
|
() => {
|
|
minimap.value?.update();
|
|
}
|
|
);
|
|
|
|
return () => {
|
|
return (
|
|
<container loc={p.loc}>
|
|
<g-rectr
|
|
loc={[10, 10, 160, 24]}
|
|
circle={[6]}
|
|
fillStyle={skillColor.ref.value}
|
|
onClick={changeAutoSkill}
|
|
cursor="pointer"
|
|
></g-rectr>
|
|
<text
|
|
loc={central(22)}
|
|
text={skill.value}
|
|
font={font1}
|
|
onClick={changeAutoSkill}
|
|
cursor="pointer"
|
|
/>
|
|
<TextContent
|
|
loc={[10, 42, 160, 60]}
|
|
text={skillDesc.value}
|
|
fontFamily="normal"
|
|
fontSize={14}
|
|
width={160}
|
|
lineHeight={4}
|
|
></TextContent>
|
|
<g-line
|
|
line={[0, 107, 180, 107]}
|
|
strokeStyle="#888"
|
|
lineWidth={1}
|
|
zIndex={-20}
|
|
></g-line>
|
|
<Scroll loc={[0, 107, 180, 100]}>
|
|
{miscData.value
|
|
.map((v, i) => {
|
|
return [
|
|
<text
|
|
text={v.name}
|
|
loc={middle(10, 16 + i * 22)}
|
|
fillStyle={v.nameColor}
|
|
font={font2}
|
|
></text>,
|
|
<text
|
|
text={v.value}
|
|
loc={middle(100, 16 + i * 22)}
|
|
fillStyle={v.valueColor}
|
|
font={font2}
|
|
></text>
|
|
];
|
|
})
|
|
.flat()}
|
|
</Scroll>
|
|
<g-line
|
|
line={[0, 207, 180, 207]}
|
|
strokeStyle="#888"
|
|
lineWidth={1}
|
|
zIndex={-20}
|
|
></g-line>
|
|
<sprite
|
|
ref={minimap}
|
|
loc={[10, 207, 160, 160]}
|
|
render={drawMinimap}
|
|
></sprite>
|
|
<g-line
|
|
line={[0, 367, 180, 367]}
|
|
strokeStyle="#888"
|
|
lineWidth={1}
|
|
zIndex={-20}
|
|
></g-line>
|
|
{inNumpad.value ? (
|
|
<NumpadToolbar
|
|
loc={[0, 367, 180, 113]}
|
|
onNumpad={onNumpad}
|
|
/>
|
|
) : s.replaying ? (
|
|
<ReplayingToolbar
|
|
loc={[0, 367, 180, 113]}
|
|
status={s.replayStatus}
|
|
/>
|
|
) : (
|
|
<PlayingToolbar
|
|
loc={[0, 367, 180, 113]}
|
|
onNumpad={onNumpad}
|
|
/>
|
|
)}
|
|
</container>
|
|
);
|
|
};
|
|
},
|
|
statusBarProps
|
|
);
|
|
|
|
export const leftStatusBarUI = new GameUI('left-status-bar', LeftStatusBar);
|
|
export const rightStatusBarUI = new GameUI('right-status-bar', RightStatusBar);
|