mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
feat: 工具栏 toolbar
This commit is contained in:
parent
91e448fdcc
commit
ea7ede3748
@ -1298,9 +1298,9 @@ control.prototype.triggerReplay = function () {
|
||||
control.prototype.pauseReplay = function () {
|
||||
if (!core.isPlaying() || !core.isReplaying()) return;
|
||||
core.status.replay.pausing = true;
|
||||
core.updateStatusBar(false, true);
|
||||
core.drawTip('暂停播放');
|
||||
Mota.require('var', 'hook').emit('replayStatus', false);
|
||||
core.updateStatusBar(false, true);
|
||||
};
|
||||
|
||||
////// 恢复播放 //////
|
||||
@ -1311,10 +1311,10 @@ control.prototype.resumeReplay = function () {
|
||||
return core.drawTip('请等待当前事件的处理结束');
|
||||
}
|
||||
core.status.replay.pausing = false;
|
||||
core.updateStatusBar(false, true);
|
||||
core.drawTip('恢复播放');
|
||||
core.replay();
|
||||
Mota.require('var', 'hook').emit('replayStatus', true);
|
||||
core.updateStatusBar(false, true);
|
||||
};
|
||||
|
||||
////// 单步播放 //////
|
||||
@ -1341,7 +1341,7 @@ control.prototype.speedUpReplay = function () {
|
||||
break;
|
||||
}
|
||||
}
|
||||
core.drawTip('x' + core.status.replay.speed + '倍');
|
||||
core.updateStatusBar(false, true);
|
||||
};
|
||||
|
||||
////// 减速播放 //////
|
||||
@ -1354,7 +1354,7 @@ control.prototype.speedDownReplay = function () {
|
||||
break;
|
||||
}
|
||||
}
|
||||
core.drawTip('x' + core.status.replay.speed + '倍');
|
||||
core.updateStatusBar(false, true);
|
||||
};
|
||||
|
||||
////// 设置播放速度 //////
|
||||
|
@ -99,6 +99,7 @@ export class Focus<T = any> extends EventEmitter<FocusEvent<T>> {
|
||||
this.emit('splice', []);
|
||||
return;
|
||||
}
|
||||
if (!this.stack[index]) return;
|
||||
const last = this.stack.at(-1) ?? null;
|
||||
if (!last) this.unfocus();
|
||||
else this.focus(last);
|
||||
|
@ -941,10 +941,17 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
event: IActionEvent,
|
||||
transform: Transform
|
||||
): vec3 {
|
||||
const x = event.offsetX + this.anchorX * this.width;
|
||||
const y = event.offsetY + this.anchorY * this.height;
|
||||
if (this.type === 'absolute') return [x, y, 0];
|
||||
else return transform.untransformed(x, y);
|
||||
const ax = this.anchorX * this.width;
|
||||
const ay = this.anchorY * this.height;
|
||||
if (this.type === 'absolute') {
|
||||
return [event.offsetX + ax, event.offsetY + ay, 0];
|
||||
} else {
|
||||
const [tx, ty] = transform.untransformed(
|
||||
event.offsetX,
|
||||
event.offsetY
|
||||
);
|
||||
return [tx + ax, ty + ay, 0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1187,6 +1194,11 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this._transform.setRotate(nextValue);
|
||||
return;
|
||||
}
|
||||
case 'noevent': {
|
||||
if (!this.assertType(nextValue, 'boolean', key)) return;
|
||||
this.noEvent = nextValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const ev = this.parseEvent(key);
|
||||
if (ev) {
|
||||
|
@ -105,8 +105,8 @@ export abstract class GraphicItemBase
|
||||
implements Required<ILineProperty>
|
||||
{
|
||||
mode: GraphicMode = GraphicMode.Fill;
|
||||
fill: CanvasStyle = '#fff';
|
||||
stroke: CanvasStyle = '#fff';
|
||||
fill: CanvasStyle = '#ddd';
|
||||
stroke: CanvasStyle = '#ddd';
|
||||
lineWidth: number = 2;
|
||||
lineDash: number[] = [];
|
||||
lineDashOffset: number = 0;
|
||||
@ -120,6 +120,7 @@ export abstract class GraphicItemBase
|
||||
private strokeAndFill: boolean = false;
|
||||
private propFillSet: boolean = false;
|
||||
|
||||
private actionStroke: boolean = false;
|
||||
private cachePath?: Path2D;
|
||||
protected pathDirty: boolean = false;
|
||||
|
||||
@ -172,11 +173,13 @@ export abstract class GraphicItemBase
|
||||
ctx.lineCap = this.lineCap;
|
||||
ctx.lineJoin = this.lineJoin;
|
||||
ctx.setLineDash(this.lineDash);
|
||||
if (this.actionStroke) {
|
||||
return ctx.isPointInStroke(path, fixX, fixY);
|
||||
}
|
||||
switch (this.mode) {
|
||||
case GraphicMode.Fill:
|
||||
return ctx.isPointInPath(path, fixX, fixY, this.fillRule);
|
||||
case GraphicMode.Stroke:
|
||||
return ctx.isPointInStroke(path, fixX, fixY);
|
||||
case GraphicMode.FillAndStroke:
|
||||
case GraphicMode.StrokeAndFill:
|
||||
return (
|
||||
@ -351,6 +354,10 @@ export abstract class GraphicItemBase
|
||||
this.miterLimit = nextValue;
|
||||
this.update();
|
||||
return true;
|
||||
case 'actionStroke':
|
||||
if (!this.assertType(nextValue, 'boolean', key)) return false;
|
||||
this.actionStroke = nextValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ export * from './hero';
|
||||
export * from './layer';
|
||||
export * from './misc';
|
||||
export * from './viewport';
|
||||
export * from './graphics';
|
||||
|
@ -97,6 +97,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
// 画布监听
|
||||
const canvas = this.target.canvas;
|
||||
canvas.addEventListener('mousedown', ev => {
|
||||
ev.preventDefault();
|
||||
const mouse = this.getMouseType(ev);
|
||||
this.lastMouse = mouse;
|
||||
this.captureEvent(
|
||||
@ -105,11 +106,13 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
);
|
||||
});
|
||||
canvas.addEventListener('mouseup', ev => {
|
||||
ev.preventDefault();
|
||||
const event = this.createMouseAction(ev, ActionType.Up);
|
||||
this.captureEvent(ActionType.Up, event);
|
||||
this.captureEvent(ActionType.Click, event);
|
||||
});
|
||||
canvas.addEventListener('mousemove', ev => {
|
||||
ev.preventDefault();
|
||||
const event = this.createMouseAction(
|
||||
ev,
|
||||
ActionType.Move,
|
||||
@ -131,6 +134,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
);
|
||||
});
|
||||
canvas.addEventListener('mouseleave', ev => {
|
||||
ev.preventDefault();
|
||||
this.hoveredElement.forEach(v => {
|
||||
v.emit('leave', this.createMouseActionBase(ev, v));
|
||||
});
|
||||
@ -138,11 +142,13 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
this.beforeHovered.clear();
|
||||
});
|
||||
document.addEventListener('touchstart', ev => {
|
||||
ev.preventDefault();
|
||||
this.createTouchAction(ev, ActionType.Down).forEach(v => {
|
||||
this.captureEvent(ActionType.Down, v);
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchend', ev => {
|
||||
ev.preventDefault();
|
||||
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||
this.captureEvent(ActionType.Up, v);
|
||||
this.captureEvent(ActionType.Click, v);
|
||||
@ -150,12 +156,14 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchcancel', ev => {
|
||||
ev.preventDefault();
|
||||
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||
this.captureEvent(ActionType.Up, v);
|
||||
this.touchInfo.delete(v.identifier);
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchmove', ev => {
|
||||
ev.preventDefault();
|
||||
this.createTouchAction(ev, ActionType.Move).forEach(v => {
|
||||
const touch = this.touchInfo.get(v.identifier);
|
||||
if (!touch) return;
|
||||
@ -172,6 +180,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
});
|
||||
});
|
||||
canvas.addEventListener('wheel', ev => {
|
||||
ev.preventDefault();
|
||||
this.captureEvent(
|
||||
ActionType.Wheel,
|
||||
this.createWheelAction(ev, ActionType.Wheel)
|
||||
|
@ -92,6 +92,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
||||
nextSibling: function (
|
||||
node: RenderItem<ERenderItemEvent>
|
||||
): RenderItem<ERenderItemEvent> | null {
|
||||
if (!node) return null;
|
||||
if (!node.parent) {
|
||||
return null;
|
||||
} else {
|
||||
|
@ -78,6 +78,8 @@ export interface BaseProps {
|
||||
scale?: ElementScale;
|
||||
/** 旋转属性,单位弧度,是 transform 的简写属性之一 */
|
||||
rotate?: number;
|
||||
/** 这个元素是否不会触发任何交互事件(cursor 属性也会无效),当执行到此元素时,会下穿至下一个元素 */
|
||||
noevent?: boolean;
|
||||
}
|
||||
|
||||
export interface SpriteProps extends BaseProps {
|
||||
@ -155,6 +157,8 @@ export interface GraphicPropsBase extends BaseProps, Partial<ILineProperty> {
|
||||
fillStyle?: CanvasStyle;
|
||||
/** 描边样式 */
|
||||
strokeStyle?: CanvasStyle;
|
||||
/** 在交互时,是否只检查交互位置只在描边上,对 fill, stroke, strokeAndFill 均有效 */
|
||||
actionStroke?: boolean;
|
||||
}
|
||||
|
||||
export interface RectProps extends GraphicPropsBase {}
|
||||
@ -229,7 +233,7 @@ export interface RectRProps extends GraphicPropsBase {
|
||||
*/
|
||||
circle?: RectRCircleParams;
|
||||
/**
|
||||
* 圆形圆角参数,可以填 `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]`,
|
||||
* 椭圆圆角参数,可以填 `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]`,
|
||||
* 两两一组,后三组可选,填写不同数量下的表现:
|
||||
* - 1组:每个角都是 `[rx1, ry1]` 半径的椭圆
|
||||
* - 2组:左上和右下是 `[rx1, ry1]` 半径的椭圆,右上和左下是 `[rx2, ry2]` 半径的椭圆
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { backDir, has } from '@/plugin/game/utils';
|
||||
import { loading } from '../game';
|
||||
import type { LayerDoorAnimate } from '@/core/render/preset/floor';
|
||||
import { getSkillLevel } from '@/plugin/game/skillTree';
|
||||
|
||||
/**
|
||||
* 一些零散机制的数据
|
||||
@ -62,6 +63,24 @@ export namespace HeroSkill {
|
||||
export const Shield = Skill.Shield;
|
||||
export const Jump = Skill.Jump;
|
||||
|
||||
const skillNameMap = new Map<Skill, string>([
|
||||
[Skill.Blade, '断灭之刃'],
|
||||
[Skill.Shield, '铸剑为盾'],
|
||||
[Skill.Jump, '跳跃']
|
||||
]);
|
||||
|
||||
const skillDesc = new Map<Skill, (level: number) => string>([
|
||||
[
|
||||
Skill.Blade,
|
||||
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
|
||||
],
|
||||
[
|
||||
Skill.Shield,
|
||||
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
|
||||
],
|
||||
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
|
||||
]);
|
||||
|
||||
interface SkillSave {
|
||||
autoSkill: boolean;
|
||||
learned: Skill[];
|
||||
@ -71,6 +90,29 @@ export namespace HeroSkill {
|
||||
let autoSkill = true;
|
||||
let enabled: Skill = Skill.None;
|
||||
|
||||
export function getLevel(skill: Skill = getEnabled()) {
|
||||
switch (skill) {
|
||||
case Blade:
|
||||
return getSkillLevel(2);
|
||||
case Jump:
|
||||
return learned.has(Jump) ? 1 : 0;
|
||||
case Shield:
|
||||
return getSkillLevel(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getSkillName(skill: Skill = getEnabled()) {
|
||||
return skillNameMap.get(skill) ?? '未开启技能';
|
||||
}
|
||||
|
||||
export function getSkillDesc(
|
||||
skill: Skill = getEnabled(),
|
||||
level: number = getLevel()
|
||||
) {
|
||||
return skillDesc.get(skill)?.(level) ?? '';
|
||||
}
|
||||
|
||||
export function setAutoSkill(auto: boolean) {
|
||||
autoSkill = auto;
|
||||
}
|
||||
|
503
src/module/render/components/icons.tsx
Normal file
503
src/module/render/components/icons.tsx
Normal file
@ -0,0 +1,503 @@
|
||||
import { DefaultProps, ElementLocator, GraphicPropsBase } from '@/core/render';
|
||||
import { computed, defineComponent, onMounted, Ref, ref, watch } from 'vue';
|
||||
import { SetupComponentOptions } from './types';
|
||||
|
||||
export interface IconsProps extends DefaultProps<GraphicPropsBase> {
|
||||
loc: ElementLocator;
|
||||
}
|
||||
|
||||
const iconsProps = {
|
||||
props: ['loc']
|
||||
} satisfies SetupComponentOptions<IconsProps>;
|
||||
|
||||
type PathGenerator = (width: number, height: number) => Path2D;
|
||||
/**
|
||||
* @param ox 横向偏移坐标
|
||||
* @param oy 纵向偏移坐标
|
||||
* @param width 路径宽度
|
||||
* @param height 路径高度
|
||||
*/
|
||||
type PathFn = (ox: number, oy: number, width: number, height: number) => Path2D;
|
||||
|
||||
function adjustPath(
|
||||
aspect: number,
|
||||
ref: Ref<Path2D | undefined>,
|
||||
fn: PathFn
|
||||
): PathGenerator {
|
||||
let beforeWidth = 200;
|
||||
let beforeHeight = 200;
|
||||
let path: Path2D | undefined;
|
||||
return (width, height) => {
|
||||
if (width === beforeWidth && height === beforeHeight && path) {
|
||||
return path;
|
||||
}
|
||||
beforeWidth = width;
|
||||
beforeHeight = height;
|
||||
const eleAspect = width / height;
|
||||
let ox = 0;
|
||||
let oy = 0;
|
||||
let dw = 0;
|
||||
let dh = 0;
|
||||
if (aspect >= eleAspect) {
|
||||
ox = 0;
|
||||
dw = width;
|
||||
dh = width / aspect;
|
||||
oy = (height - dh) / 2;
|
||||
} else {
|
||||
oy = 0;
|
||||
dh = height;
|
||||
dw = height * aspect;
|
||||
ox = (width - dw) / 2;
|
||||
}
|
||||
path = fn(ox, oy, dw, dh);
|
||||
ref.value = path;
|
||||
return path;
|
||||
};
|
||||
}
|
||||
|
||||
export const RollbackIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const arc = width / 10;
|
||||
const arrow = width / 10;
|
||||
const left = ox + width / 10;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 10;
|
||||
const bottom = oy + height - height / 5;
|
||||
const end = left + width / 4;
|
||||
path.moveTo(left, bottom);
|
||||
path.lineTo(right - arc, bottom);
|
||||
path.arcTo(right, bottom, right, bottom - arc, arc);
|
||||
path.lineTo(right, top + arc);
|
||||
path.arcTo(right, top, right - arc, top, arc);
|
||||
path.lineTo(end, top);
|
||||
path.moveTo(end + arrow, top - arrow);
|
||||
path.lineTo(end, top);
|
||||
path.lineTo(end + arrow, top + arrow);
|
||||
path.moveTo(left, top);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const RetweetIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const arc = width / 10;
|
||||
const arrow = width / 10;
|
||||
const left = ox + width / 10;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 10;
|
||||
const bottom = oy + height - height / 5;
|
||||
const end = left + width / 2;
|
||||
path.moveTo(end, bottom);
|
||||
path.lineTo(left + arc, bottom);
|
||||
path.arcTo(left, bottom, left, bottom - arc, arc);
|
||||
path.lineTo(left, top + arc);
|
||||
path.arcTo(left, top, left + arc, top, arc);
|
||||
path.lineTo(right, top);
|
||||
path.moveTo(right - arrow, top - arrow);
|
||||
path.lineTo(right, top);
|
||||
path.lineTo(right - arrow, top + arrow);
|
||||
path.moveTo(left, top);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const ViewMapIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
const cx = ox + width / 2;
|
||||
const cy = oy + height / 2;
|
||||
path.rect(left, top, right - left, bottom - top);
|
||||
path.moveTo(cx, top);
|
||||
path.lineTo(cx, bottom);
|
||||
path.moveTo(left, cy);
|
||||
path.lineTo(right, cy);
|
||||
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const DanmakuIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
const cx = ox + width / 2;
|
||||
const cy = oy + height / 2;
|
||||
const rx = width / 3;
|
||||
const ry = height / 4;
|
||||
const start = (Math.PI * 16) / 18;
|
||||
const end = (Math.PI * 12) / 18;
|
||||
path.ellipse(cx, cy, rx, ry, 0, start, end);
|
||||
path.lineTo(left - width / 24, bottom - height / 36);
|
||||
path.closePath();
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const ReplayIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const arc = width / 10;
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
const cy = oy + height / 2;
|
||||
path.moveTo(right, cy - height / 8);
|
||||
path.lineTo(right, top + arc);
|
||||
path.arcTo(right, top, right - arc, top, arc);
|
||||
path.lineTo(left + arc, top);
|
||||
path.arcTo(left, top, left, top + arc, arc);
|
||||
path.lineTo(left, cy);
|
||||
path.moveTo(left + arc, cy - arc);
|
||||
path.lineTo(left, cy);
|
||||
path.lineTo(left - arc, cy - arc);
|
||||
path.moveTo(left, cy + height / 8);
|
||||
path.lineTo(left, bottom - arc);
|
||||
path.arcTo(left, bottom, left + arc, bottom, arc);
|
||||
path.lineTo(right - arc, bottom);
|
||||
path.arcTo(right, bottom, right, bottom - arc, arc);
|
||||
path.lineTo(right, cy);
|
||||
path.moveTo(right - arc, cy + arc);
|
||||
path.lineTo(right, cy);
|
||||
path.lineTo(right + arc, cy + arc);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const NumpadIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
const cx = ox + width / 2;
|
||||
const cy = oy + height / 2;
|
||||
path.rect(left, top, right - left, bottom - top);
|
||||
const path2 = new Path2D();
|
||||
path2.ellipse(cx, cy, width / 9, height / 6, 0, 0, Math.PI * 2);
|
||||
path.addPath(path2);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const PlayIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
path.moveTo(left, top);
|
||||
path.lineTo(right, oy + height / 2);
|
||||
path.lineTo(left, bottom);
|
||||
path.closePath();
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
fill
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const PauseIcon = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const cx = ox + width / 2;
|
||||
const top = oy + height / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
path.moveTo(cx - width / 5, top);
|
||||
path.lineTo(cx - width / 5, bottom);
|
||||
path.moveTo(cx + width / 5, top);
|
||||
path.lineTo(cx + width / 5, bottom);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const DoubleArrow = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const path2 = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
const cx = ox + width / 2;
|
||||
const cy = oy + height / 2;
|
||||
path.moveTo(left, top + height / 12);
|
||||
path.lineTo(cx + width / 8, cy);
|
||||
path.lineTo(left, bottom - height / 12);
|
||||
path.closePath();
|
||||
path2.moveTo(cx, top + height / 12);
|
||||
path2.lineTo(right, cy);
|
||||
path2.lineTo(cx, bottom - height / 12);
|
||||
path2.closePath();
|
||||
path.addPath(path2);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
fill
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const StepForward = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const path2 = new Path2D();
|
||||
const left = ox + width / 5;
|
||||
const top = oy + height / 5;
|
||||
const right = ox + width - width / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
path.moveTo(left, top);
|
||||
path.lineTo(right, oy + height / 2);
|
||||
path.lineTo(left, bottom);
|
||||
path.closePath();
|
||||
path2.moveTo(right, top);
|
||||
path2.lineTo(right, bottom);
|
||||
path.addPath(path2);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
fill
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
@ -12,6 +12,8 @@ interface ProgressProps extends DefaultProps {
|
||||
success?: CanvasStyle;
|
||||
/** 未完成部分的样式,默认为灰色(gray) */
|
||||
background?: CanvasStyle;
|
||||
/** 线宽度 */
|
||||
lineWidth?: number;
|
||||
}
|
||||
|
||||
const progressProps = {
|
||||
@ -25,10 +27,22 @@ export const Progress = defineComponent<ProgressProps>(props => {
|
||||
const { ctx } = canvas;
|
||||
const width = props.loc[2] ?? 200;
|
||||
const height = props.loc[3] ?? 200;
|
||||
ctx.fillStyle = props.background ?? 'gray';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.fillStyle = props.success ?? 'green';
|
||||
ctx.fillRect(0, 0, width * props.progress, height);
|
||||
ctx.lineCap = 'round';
|
||||
const lineWidth = props.lineWidth ?? 2;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.strokeStyle = props.background ?? 'gray';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lineWidth, height / 2);
|
||||
ctx.lineTo(width - lineWidth, height / 2);
|
||||
ctx.stroke();
|
||||
if (!isNaN(props.progress)) {
|
||||
ctx.strokeStyle = props.success ?? 'green';
|
||||
const p = lineWidth + (width - lineWidth * 2) * props.progress;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lineWidth, height / 2);
|
||||
ctx.lineTo(p, height / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
watch(props, () => {
|
||||
|
@ -211,7 +211,7 @@ const textboxOptions = {
|
||||
'hidden',
|
||||
'anchorX',
|
||||
'anchorY',
|
||||
'antiAliasing',
|
||||
'anti',
|
||||
'cache',
|
||||
'composite',
|
||||
'fall',
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
RightStatusBar
|
||||
} from './statusBar';
|
||||
import { onLoaded } from '../use';
|
||||
import { ReplayingStatus } from './toolbar';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
@ -92,12 +93,29 @@ const MainScene = defineComponent(() => {
|
||||
exAtk: 0,
|
||||
magicDef: 0
|
||||
});
|
||||
const rightStatus: IRightHeroStatus = reactive({});
|
||||
const replayStatus: ReplayingStatus = reactive({
|
||||
playing: false,
|
||||
speed: 1,
|
||||
played: 0,
|
||||
total: 0
|
||||
});
|
||||
const rightStatus: IRightHeroStatus = reactive({
|
||||
autoSkill: false,
|
||||
skillName: '',
|
||||
skillDesc: '',
|
||||
jumpCount: 0,
|
||||
springCount: 0,
|
||||
floor: 'MT0',
|
||||
replaying: false,
|
||||
replayStatus
|
||||
});
|
||||
|
||||
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||
|
||||
const updateStatus = () => {
|
||||
if (!core.status || !core.status.hero || !core.status.floorId) return;
|
||||
const hero = core.status.hero;
|
||||
const floor = core.status.floorId;
|
||||
leftStatus.atk = getHeroStatusOn('atk');
|
||||
leftStatus.hp = getHeroStatusOn('hp');
|
||||
leftStatus.def = getHeroStatusOn('def');
|
||||
@ -112,6 +130,32 @@ const MainScene = defineComponent(() => {
|
||||
leftStatus.regen = getHeroStatusOn('hpmax');
|
||||
leftStatus.exAtk = getHeroStatusOn('mana');
|
||||
leftStatus.magicDef = getHeroStatusOn('magicDef');
|
||||
|
||||
const { HeroSkill } = Mota.require('module', 'Mechanism');
|
||||
rightStatus.autoSkill = HeroSkill.getAutoSkill();
|
||||
rightStatus.skillName = HeroSkill.getSkillName();
|
||||
rightStatus.skillDesc = HeroSkill.getSkillDesc();
|
||||
rightStatus.floor = floor;
|
||||
rightStatus.replaying = core.isReplaying();
|
||||
const { pausing, speed, toReplay, totalList } = core.status.replay;
|
||||
replayStatus.playing = !pausing;
|
||||
replayStatus.speed = speed;
|
||||
replayStatus.played = totalList.length - toReplay.length;
|
||||
replayStatus.total = totalList.length;
|
||||
if (HeroSkill.learnedSkill(HeroSkill.Jump)) {
|
||||
if (Mota.Plugin.require('skill_g').jumpIgnoreFloor.has(floor)) {
|
||||
rightStatus.jumpCount = -2;
|
||||
} else {
|
||||
rightStatus.jumpCount = 3 - (flags[`jump_${floor}`] ?? 0);
|
||||
}
|
||||
} else {
|
||||
rightStatus.jumpCount = -1;
|
||||
}
|
||||
if (core.hasFlag('spring')) {
|
||||
rightStatus.springCount = 50 - (flags.springCount ?? 0);
|
||||
} else {
|
||||
rightStatus.springCount = -1;
|
||||
}
|
||||
};
|
||||
|
||||
const loaded = ref(false);
|
||||
@ -129,6 +173,7 @@ const MainScene = defineComponent(() => {
|
||||
status={leftStatus}
|
||||
></LeftStatusBar>
|
||||
)}
|
||||
<g-line line={[180, 0, 180, 480]} lineWidth={1} />
|
||||
<container id="map-draw" {...mapDrawProps} x={180} zIndex={10}>
|
||||
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||
<layer layer="bg" zIndex={10}></layer>
|
||||
@ -141,6 +186,7 @@ const MainScene = defineComponent(() => {
|
||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||
</container>
|
||||
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
|
||||
{loaded.value && (
|
||||
<RightStatusBar
|
||||
loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||
@ -153,6 +199,12 @@ const MainScene = defineComponent(() => {
|
||||
>
|
||||
{mainUIController.render()}
|
||||
</container>
|
||||
<g-rect
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
zIndex={100}
|
||||
stroke
|
||||
noevent
|
||||
></g-rect>
|
||||
</container>
|
||||
);
|
||||
});
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { GameUI } from '@/core/system';
|
||||
import { defineComponent } from 'vue';
|
||||
import { SetupComponentOptions } from '../components';
|
||||
import { ElementLocator } from '@/core/render';
|
||||
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;
|
||||
@ -24,8 +34,6 @@ export interface ILeftHeroStatus {
|
||||
magicDef: number;
|
||||
}
|
||||
|
||||
export interface IRightHeroStatus {}
|
||||
|
||||
interface StatusBarProps<T> {
|
||||
loc: ElementLocator;
|
||||
status: T;
|
||||
@ -47,7 +55,7 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||
const s = p.status;
|
||||
const f = core.formatBigNumber;
|
||||
|
||||
const floorName = core.floors[s.floor].title;
|
||||
const floorName = computed(() => core.floors[s.floor]?.title ?? '');
|
||||
|
||||
const key = (num: number) => {
|
||||
return num.toString().padStart(2, '0');
|
||||
@ -86,9 +94,8 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||
return () => {
|
||||
return (
|
||||
<container loc={p.loc}>
|
||||
<g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
|
||||
<text
|
||||
text={floorName}
|
||||
text={floorName.value}
|
||||
loc={central(24)}
|
||||
font={font1}
|
||||
cursor="pointer"
|
||||
@ -163,13 +170,228 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||
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-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
|
||||
<Scroll loc={[0, 0, 180, 100]}></Scroll>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
364
src/module/render/ui/toolbar.tsx
Normal file
364
src/module/render/ui/toolbar.tsx
Normal file
@ -0,0 +1,364 @@
|
||||
import { DefaultProps, ElementLocator } from '@/core/render';
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
import { SetupComponentOptions } from '../components';
|
||||
import {
|
||||
DanmakuIcon,
|
||||
DoubleArrow,
|
||||
NumpadIcon,
|
||||
PauseIcon,
|
||||
PlayIcon,
|
||||
ReplayIcon,
|
||||
RetweetIcon,
|
||||
RollbackIcon,
|
||||
StepForward,
|
||||
ViewMapIcon
|
||||
} from '../components/icons';
|
||||
import {
|
||||
generateBinary,
|
||||
getVitualKeyOnce,
|
||||
openDanmakuPoster
|
||||
} from '@/plugin/utils';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { generateKeyboardEvent } from '@/core/main/custom/keyboard';
|
||||
import { transitioned } from '../use';
|
||||
import { linear } from 'mutate-animate';
|
||||
import { KeyCode } from '@/plugin/keyCodes';
|
||||
import { Progress } from '../components/misc';
|
||||
|
||||
interface ToolbarProps extends DefaultProps {
|
||||
loc?: ElementLocator;
|
||||
}
|
||||
|
||||
type ToolbarEmits = {
|
||||
numpad: () => void;
|
||||
};
|
||||
|
||||
const toolbarProps = {
|
||||
props: ['loc'],
|
||||
emits: ['numpad']
|
||||
} satisfies SetupComponentOptions<
|
||||
ToolbarProps,
|
||||
ToolbarEmits,
|
||||
keyof ToolbarEmits
|
||||
>;
|
||||
|
||||
const im = (col: number, row: number): ElementLocator => {
|
||||
return [5 + 34 * col, 5 + 36 * row, 32, 32];
|
||||
};
|
||||
|
||||
const ic = (col: number, row: number): ElementLocator => {
|
||||
return [7 + 34 * col, 7 + 36 * row, 28, 28];
|
||||
};
|
||||
|
||||
const ic2 = (col: number, row: number): ElementLocator => {
|
||||
return [9 + 34 * col, 9 + 36 * row, 24, 24];
|
||||
};
|
||||
|
||||
const middle = (col: number, row: number): ElementLocator => {
|
||||
return [21 + 34 * col, 21 + 36 * row, void 0, void 0, 0.5, 0.5];
|
||||
};
|
||||
|
||||
const middle2 = (
|
||||
col: number,
|
||||
row: number,
|
||||
width: number,
|
||||
height: number
|
||||
): ElementLocator => {
|
||||
return [21 + 34 * col, 21 + 36 * row, width, height, 0.5, 0.5];
|
||||
};
|
||||
|
||||
export const PlayingToolbar = defineComponent<
|
||||
ToolbarProps,
|
||||
ToolbarEmits,
|
||||
keyof ToolbarEmits
|
||||
>((props, { emit }) => {
|
||||
const bookIcon = core.statusBar.icons.book;
|
||||
const flyIcon = core.statusBar.icons.fly;
|
||||
const toolIcon = core.statusBar.icons.toolbox;
|
||||
const equipIcon = core.statusBar.icons.equipbox;
|
||||
const keyIcon = core.statusBar.icons.keyboard;
|
||||
const shopIcon = core.statusBar.icons.shop;
|
||||
const saveIcon = core.statusBar.icons.save;
|
||||
const loadIcon = core.statusBar.icons.load;
|
||||
const setIcon = core.statusBar.icons.settings;
|
||||
|
||||
const iconFont = '12px Verdana';
|
||||
|
||||
const book = () => core.openBook(true);
|
||||
const tool = () => core.openEquipbox(true);
|
||||
const fly = () => core.useFly(true);
|
||||
const save = () => core.save(true);
|
||||
const load = () => core.load(true);
|
||||
const equip = () => core.openEquipbox(true);
|
||||
const shop = () => core.openQuickShop(true);
|
||||
const key = () => {
|
||||
getVitualKeyOnce().then(value => {
|
||||
gameKey.emitKey(
|
||||
value.key,
|
||||
value.assist,
|
||||
'up',
|
||||
generateKeyboardEvent(value.key, value.assist)
|
||||
);
|
||||
});
|
||||
};
|
||||
const undo = () => core.doSL('autoSave', 'load');
|
||||
const redo = () => core.doSL('autoSave', 'reload');
|
||||
const numpad = () => emit('numpad');
|
||||
const view = () => {
|
||||
if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) {
|
||||
core.ui._drawViewMaps();
|
||||
}
|
||||
};
|
||||
const danmaku = () => requestAnimationFrame(openDanmakuPoster);
|
||||
const replay = () => core.ui._drawReplay();
|
||||
const settings = () => core.openSettings(true);
|
||||
|
||||
return () => (
|
||||
<container loc={props.loc} cursor="pointer">
|
||||
<image image={bookIcon} loc={im(0, 0)} noanti onClick={book} />
|
||||
<image image={toolIcon} loc={im(1, 0)} noanti onClick={tool} />
|
||||
<image image={flyIcon} loc={im(2, 0)} noanti onClick={fly} />
|
||||
<image image={saveIcon} loc={im(3, 0)} noanti onClick={save} />
|
||||
<image image={loadIcon} loc={im(4, 0)} noanti onClick={load} />
|
||||
<image image={equipIcon} loc={im(0, 1)} noanti onClick={equip} />
|
||||
<image image={shopIcon} loc={im(1, 1)} noanti onClick={shop} />
|
||||
<image image={keyIcon} loc={im(2, 1)} noanti onClick={key} />
|
||||
<RollbackIcon loc={ic(3, 1)} strokeStyle="#eee" onClick={undo} />
|
||||
<RetweetIcon loc={ic(4, 1)} strokeStyle="#eee" onClick={redo} />
|
||||
<NumpadIcon loc={ic(0, 2)} strokeStyle="#eee" onClick={numpad} />
|
||||
<ViewMapIcon loc={ic(1, 2)} strokeStyle="#eee" onClick={view} />
|
||||
<DanmakuIcon loc={ic(2, 2)} strokeStyle="#eee" onClick={danmaku} />
|
||||
<ReplayIcon loc={ic(3, 2)} strokeStyle="#eee" onClick={replay} />
|
||||
<text text="R" loc={middle(3, 2)} font={iconFont} noevent />
|
||||
<image image={setIcon} loc={im(4, 2)} noanti onClick={settings} />
|
||||
</container>
|
||||
);
|
||||
}, toolbarProps);
|
||||
|
||||
export interface ReplayingStatus {
|
||||
/** 是否正在播放 */
|
||||
playing: boolean;
|
||||
/** 录像播放速度 */
|
||||
speed: number;
|
||||
/** 已播放的长度 */
|
||||
played: number;
|
||||
/** 总长度 */
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ReplayingProps extends ToolbarProps {
|
||||
/** 录像播放状态 */
|
||||
status: ReplayingStatus;
|
||||
}
|
||||
|
||||
const replayingProps = {
|
||||
props: ['status', 'loc']
|
||||
} satisfies SetupComponentOptions<ReplayingProps>;
|
||||
|
||||
export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
|
||||
const status = props.status;
|
||||
|
||||
const bookIcon = core.statusBar.icons.book;
|
||||
const saveIcon = core.statusBar.icons.save;
|
||||
const font1 = '16px normal';
|
||||
const font2 = '12px Verdana';
|
||||
|
||||
const speedText = computed(() => `${status.speed}速`);
|
||||
const progress = computed(() => status.played / status.total);
|
||||
const progressText1 = computed(() => `${status.played}/${status.total}`);
|
||||
const progressText2 = computed(
|
||||
() => `${(progress.value * 100).toFixed(2)}%`
|
||||
);
|
||||
|
||||
const play = () => core.resumeReplay();
|
||||
const pause = () => core.pauseReplay();
|
||||
const stop = () => core.stopReplay(true);
|
||||
const speedDown = () => core.speedDownReplay();
|
||||
const speedUp = () => core.speedUpReplay();
|
||||
const book = () => core.openBook(true);
|
||||
const save = () => core.save(true);
|
||||
const view = () => {
|
||||
if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) {
|
||||
core.ui._drawViewMaps();
|
||||
}
|
||||
};
|
||||
const rewind = () => core.rewindReplay();
|
||||
const step = () => core.stepReplay();
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<container loc={props.loc} cursor="pointer">
|
||||
{status.playing ? (
|
||||
<PauseIcon loc={ic(0, 0)} onClick={pause} />
|
||||
) : (
|
||||
<PlayIcon loc={ic(0, 0)} onClick={play} />
|
||||
)}
|
||||
<g-rectr loc={[47, 13, 16, 16]} circle={[2]} onClick={stop} />
|
||||
<DoubleArrow
|
||||
loc={middle2(2, 0, 28, 28)}
|
||||
scale={[-1, 1]}
|
||||
onClick={speedDown}
|
||||
/>
|
||||
<text text={speedText.value} loc={middle(3, 0)} font={font1} />
|
||||
<DoubleArrow loc={ic(4, 0)} onClick={speedUp} />
|
||||
<image image={bookIcon} loc={im(0, 1)} noanti onClick={book} />
|
||||
<image image={saveIcon} loc={im(1, 1)} noanti onClick={save} />
|
||||
<ViewMapIcon loc={ic(2, 1)} onClick={view} />
|
||||
<StepForward
|
||||
loc={middle2(3, 1, 28, 28)}
|
||||
scale={[-1, 1]}
|
||||
onClick={rewind}
|
||||
/>
|
||||
<StepForward loc={ic(4, 1)} onClick={step} />
|
||||
<text
|
||||
text={progressText1.value}
|
||||
loc={[12, 98, void 0, void 0, 0, 1]}
|
||||
font={font2}
|
||||
/>
|
||||
<text
|
||||
text={progressText2.value}
|
||||
loc={[168, 98, void 0, void 0, 1, 1]}
|
||||
font={font2}
|
||||
/>
|
||||
<Progress
|
||||
loc={[12, 101, 156, 4]}
|
||||
progress={progress.value}
|
||||
success="lightgreen"
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
};
|
||||
}, replayingProps);
|
||||
|
||||
export const NumpadToolbar = defineComponent<
|
||||
ToolbarProps,
|
||||
ToolbarEmits,
|
||||
keyof ToolbarEmits
|
||||
>((props, { emit }) => {
|
||||
const numpad = () => emit('numpad');
|
||||
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
||||
|
||||
const ctrlEnabled = ref(false);
|
||||
const shiftEnabled = ref(false);
|
||||
const altEnabled = ref(false);
|
||||
|
||||
const ctrlAlpha = transitioned(0, 100, linear())!;
|
||||
const shiftAlpha = transitioned(0, 100, linear())!;
|
||||
const altAlpha = transitioned(0, 100, linear())!;
|
||||
|
||||
const ctrlColor = computed(
|
||||
() => `rgba(255,255,255,${ctrlAlpha.ref.value})`
|
||||
);
|
||||
const ctrlTextColor = computed(() => {
|
||||
const rgb = Math.floor(255 - ctrlAlpha.ref.value * 255);
|
||||
return `rgba(${rgb},${rgb},${rgb},1)`;
|
||||
});
|
||||
|
||||
const shiftColor = computed(
|
||||
() => `rgba(255,255,255,${shiftAlpha.ref.value})`
|
||||
);
|
||||
const shiftTextColor = computed(() => {
|
||||
const rgb = Math.floor(255 - shiftAlpha.ref.value * 255);
|
||||
return `rgba(${rgb},${rgb},${rgb},1)`;
|
||||
});
|
||||
|
||||
const altColor = computed(() => `rgba(255,255,255,${altAlpha.ref.value})`);
|
||||
const altTextColor = computed(() => {
|
||||
const rgb = Math.floor(255 - altAlpha.ref.value * 255);
|
||||
return `rgba(${rgb},${rgb},${rgb},1)`;
|
||||
});
|
||||
|
||||
const clickCtrl = () => {
|
||||
ctrlEnabled.value = !ctrlEnabled.value;
|
||||
ctrlAlpha.set(ctrlEnabled.value ? 1 : 0);
|
||||
};
|
||||
|
||||
const clickShift = () => {
|
||||
shiftEnabled.value = !shiftEnabled.value;
|
||||
shiftAlpha.set(shiftEnabled.value ? 1 : 0);
|
||||
};
|
||||
|
||||
const clickAlt = () => {
|
||||
altEnabled.value = !altEnabled.value;
|
||||
altAlpha.set(altEnabled.value ? 1 : 0);
|
||||
};
|
||||
|
||||
const clickNum = (num: number) => {
|
||||
const bin = generateBinary([
|
||||
ctrlEnabled.value,
|
||||
shiftEnabled.value,
|
||||
altEnabled.value
|
||||
]);
|
||||
const code = (KeyCode.Digit0 + num) as KeyCode;
|
||||
gameKey.emitKey(code, bin, 'up', generateKeyboardEvent(code, bin));
|
||||
};
|
||||
|
||||
return () => (
|
||||
<container loc={props.loc} cursor="pointer">
|
||||
<container loc={[0, 0, 180, 81]}>
|
||||
{nums
|
||||
.map((v, i) => {
|
||||
const col = i % 5;
|
||||
const row = Math.floor(i / 5);
|
||||
return [
|
||||
<g-rectr
|
||||
loc={ic2(col, row)}
|
||||
circle={[4]}
|
||||
stroke
|
||||
onClick={() => clickNum(v)}
|
||||
/>,
|
||||
<text
|
||||
text={v.toString()}
|
||||
loc={middle(col, row)}
|
||||
noevent
|
||||
/>
|
||||
];
|
||||
})
|
||||
.flat()}
|
||||
</container>
|
||||
<g-rectr
|
||||
loc={[41, 81, 36, 24]}
|
||||
circle={[4]}
|
||||
stroke
|
||||
fill
|
||||
fillStyle={ctrlColor.value}
|
||||
onClick={clickCtrl}
|
||||
></g-rectr>
|
||||
<text
|
||||
text="Ctrl"
|
||||
loc={[59, 93, void 0, void 0, 0.5, 0.5]}
|
||||
fillStyle={ctrlTextColor.value}
|
||||
noevent
|
||||
/>
|
||||
<g-rectr
|
||||
loc={[86, 81, 44, 24]}
|
||||
circle={[4]}
|
||||
stroke
|
||||
fill
|
||||
fillStyle={shiftColor.value}
|
||||
onClick={clickShift}
|
||||
></g-rectr>
|
||||
<text
|
||||
text="Shift"
|
||||
loc={[108, 93, void 0, void 0, 0.5, 0.5]}
|
||||
fillStyle={shiftTextColor.value}
|
||||
noevent
|
||||
/>
|
||||
<g-rectr
|
||||
loc={[139, 81, 30, 24]}
|
||||
circle={[4]}
|
||||
stroke
|
||||
fill
|
||||
fillStyle={altColor.value}
|
||||
onClick={clickAlt}
|
||||
></g-rectr>
|
||||
<text
|
||||
text="Alt"
|
||||
loc={[154, 93, void 0, void 0, 0.5, 0.5]}
|
||||
fillStyle={altTextColor.value}
|
||||
noevent
|
||||
/>
|
||||
<NumpadIcon loc={ic(0, 2)} strokeStyle="gold" onClick={numpad} />
|
||||
</container>
|
||||
);
|
||||
}, toolbarProps);
|
@ -28,8 +28,6 @@ export function init() {
|
||||
if (!core.control.noAutoEvents) core.checkAutoEvents();
|
||||
core.control._updateStatusBar_setToolboxIcon();
|
||||
core.control.noAutoEvents = true;
|
||||
// 更新vue状态栏
|
||||
updateVueStatusBar();
|
||||
Mota.require('var', 'hook').emit('statusBarUpdate');
|
||||
};
|
||||
|
||||
@ -66,10 +64,3 @@ export function init() {
|
||||
core.setFlag('showToolbox', showToolbox || null);
|
||||
};
|
||||
}
|
||||
|
||||
function updateVueStatusBar() {
|
||||
Mota.r(() => {
|
||||
const status = Mota.require('var', 'status');
|
||||
status.value = !status.value;
|
||||
});
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ export function generateBinary(arr: boolean[]) {
|
||||
let num = 0;
|
||||
arr.forEach((v, i) => {
|
||||
if (v) {
|
||||
num += 1 << i;
|
||||
num |= 1 << i;
|
||||
}
|
||||
});
|
||||
return num;
|
||||
|
Loading…
Reference in New Issue
Block a user