Compare commits

..

No commits in common. "b0e420c167c4eb76dab2c26049782536eb1aa1ff" and "91e448fdcc6970a0f0c6ea8954e03622a52e3f7c" have entirely different histories.

20 changed files with 110 additions and 1289 deletions

View File

@ -1284,6 +1284,7 @@ control.prototype.startReplay = function (list) {
// '由于不可抗力,录像播放过程中将没有勇士移动动画' // '由于不可抗力,录像播放过程中将没有勇士移动动画'
// ); // );
Mota.require('var', 'hook').emit('replayStatus', false); Mota.require('var', 'hook').emit('replayStatus', false);
Mota.require('class', 'CustomToolbar').setDefaultTool(true);
this.replay(); this.replay();
}; };
@ -1297,9 +1298,9 @@ control.prototype.triggerReplay = function () {
control.prototype.pauseReplay = function () { control.prototype.pauseReplay = function () {
if (!core.isPlaying() || !core.isReplaying()) return; if (!core.isPlaying() || !core.isReplaying()) return;
core.status.replay.pausing = true; core.status.replay.pausing = true;
core.updateStatusBar(false, true);
core.drawTip('暂停播放'); core.drawTip('暂停播放');
Mota.require('var', 'hook').emit('replayStatus', false); Mota.require('var', 'hook').emit('replayStatus', false);
core.updateStatusBar(false, true);
}; };
////// 恢复播放 ////// ////// 恢复播放 //////
@ -1310,10 +1311,10 @@ control.prototype.resumeReplay = function () {
return core.drawTip('请等待当前事件的处理结束'); return core.drawTip('请等待当前事件的处理结束');
} }
core.status.replay.pausing = false; core.status.replay.pausing = false;
core.updateStatusBar(false, true);
core.drawTip('恢复播放'); core.drawTip('恢复播放');
core.replay(); core.replay();
Mota.require('var', 'hook').emit('replayStatus', true); Mota.require('var', 'hook').emit('replayStatus', true);
core.updateStatusBar(false, true);
}; };
////// 单步播放 ////// ////// 单步播放 //////
@ -1340,7 +1341,7 @@ control.prototype.speedUpReplay = function () {
break; break;
} }
} }
core.updateStatusBar(false, true); core.drawTip('x' + core.status.replay.speed + '倍');
}; };
////// 减速播放 ////// ////// 减速播放 //////
@ -1353,7 +1354,7 @@ control.prototype.speedDownReplay = function () {
break; break;
} }
} }
core.updateStatusBar(false, true); core.drawTip('x' + core.status.replay.speed + '倍');
}; };
////// 设置播放速度 ////// ////// 设置播放速度 //////
@ -1379,6 +1380,7 @@ control.prototype.stopReplay = function (force) {
core.updateStatusBar(false, true); core.updateStatusBar(false, true);
core.drawTip('停止播放并恢复游戏'); core.drawTip('停止播放并恢复游戏');
Mota.require('var', 'hook').emit('replayStatus', true); Mota.require('var', 'hook').emit('replayStatus', true);
Mota.require('class', 'CustomToolbar').setDefaultTool(false);
}; };
////// 回退 ////// ////// 回退 //////
@ -1415,6 +1417,7 @@ control.prototype.rewindReplay = function () {
core.control._replay_drawProgress(); core.control._replay_drawProgress();
core.updateStatusBar(false, true); core.updateStatusBar(false, true);
core.drawTip('成功回退到上一个节点'); core.drawTip('成功回退到上一个节点');
Mota.require('class', 'CustomToolbar').setDefaultTool(true);
}); });
}; };

View File

@ -78,6 +78,9 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
}); });
} }
Mota.r(() => {
Mota.require('class', 'CustomToolbar').setDefaultTool(false);
});
const { NightSpecial, HeroSkill } = Mota.require( const { NightSpecial, HeroSkill } = Mota.require(
'module', 'module',
'Mechanism' 'Mechanism'

View File

@ -411,6 +411,44 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
static closeAll() { static closeAll() {
this.list.forEach(v => v.closeAll()); this.list.forEach(v => v.closeAll());
} }
/**
*
*/
static setDefaultTool(replaying: boolean) {
const mainStorage = GameStorage.for(GameStorage.fromGame('main'));
mainStorage.read();
let defaultsTool = CustomToolbar.list.find(v => v.id === '@defaults');
const hasDefaults = !!defaultsTool;
if (!defaultsTool) {
defaultsTool = new CustomToolbar('@defaults', true);
}
defaultsTool.closeAll();
defaultsTool.items.splice(0);
defaultsTool.add(replaying ? replayingDefaultTool : playingDefaultTool);
if (!mainStorage.getValue('played', false)) {
mainStorage.setValue('played', true);
// 计算位置,显示在游戏画面下方
if (!hasDefaults) {
const game = core.dom.gameDraw;
const bottom = game.offsetTop + game.offsetHeight;
const left = game.offsetLeft;
const width = game.offsetWidth;
if (isMobile) {
// 手机端显示在最下方
defaultsTool.setPos(16, bottom);
defaultsTool.setSize(window.innerWidth - 32, 85);
} else {
// 电脑显示在屏幕右方
defaultsTool.setPos(left, bottom);
defaultsTool.setSize(width, 70);
}
}
}
defaultsTool.show();
}
} }
Mota.require('var', 'loading').once('coreInit', () => { Mota.require('var', 'loading').once('coreInit', () => {
@ -427,3 +465,8 @@ Mota.require('var', 'loading').once('coreInit', () => {
Mota.require('var', 'hook').on('reset', () => { Mota.require('var', 'hook').on('reset', () => {
CustomToolbar.showAll(); CustomToolbar.showAll();
}); });
Mota.require('var', 'hook').once('reset', () => {
CustomToolbar.setDefaultTool(false);
CustomToolbar.save();
});

View File

@ -99,7 +99,6 @@ export class Focus<T = any> extends EventEmitter<FocusEvent<T>> {
this.emit('splice', []); this.emit('splice', []);
return; return;
} }
if (!this.stack[index]) return;
const last = this.stack.at(-1) ?? null; const last = this.stack.at(-1) ?? null;
if (!last) this.unfocus(); if (!last) this.unfocus();
else this.focus(last); else this.focus(last);

View File

@ -941,17 +941,10 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
event: IActionEvent, event: IActionEvent,
transform: Transform transform: Transform
): vec3 { ): vec3 {
const ax = this.anchorX * this.width; const x = event.offsetX + this.anchorX * this.width;
const ay = this.anchorY * this.height; const y = event.offsetY + this.anchorY * this.height;
if (this.type === 'absolute') { if (this.type === 'absolute') return [x, y, 0];
return [event.offsetX + ax, event.offsetY + ay, 0]; else return transform.untransformed(x, y);
} else {
const [tx, ty] = transform.untransformed(
event.offsetX,
event.offsetY
);
return [tx + ax, ty + ay, 0];
}
} }
/** /**
@ -1194,11 +1187,6 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
this._transform.setRotate(nextValue); this._transform.setRotate(nextValue);
return; return;
} }
case 'noevent': {
if (!this.assertType(nextValue, 'boolean', key)) return;
this.noEvent = nextValue;
return;
}
} }
const ev = this.parseEvent(key); const ev = this.parseEvent(key);
if (ev) { if (ev) {

View File

@ -105,8 +105,8 @@ export abstract class GraphicItemBase
implements Required<ILineProperty> implements Required<ILineProperty>
{ {
mode: GraphicMode = GraphicMode.Fill; mode: GraphicMode = GraphicMode.Fill;
fill: CanvasStyle = '#ddd'; fill: CanvasStyle = '#fff';
stroke: CanvasStyle = '#ddd'; stroke: CanvasStyle = '#fff';
lineWidth: number = 2; lineWidth: number = 2;
lineDash: number[] = []; lineDash: number[] = [];
lineDashOffset: number = 0; lineDashOffset: number = 0;
@ -114,16 +114,14 @@ export abstract class GraphicItemBase
lineCap: CanvasLineCap = 'butt'; lineCap: CanvasLineCap = 'butt';
miterLimit: number = 10; miterLimit: number = 10;
fillRule: CanvasFillRule = 'nonzero'; fillRule: CanvasFillRule = 'nonzero';
enableCache: boolean = false;
private propFill: boolean = true; private propFill: boolean = true;
private propStroke: boolean = false; private propStroke: boolean = false;
private strokeAndFill: boolean = false; private strokeAndFill: boolean = false;
private propFillSet: boolean = false; private propFillSet: boolean = false;
private actionStroke: boolean = false;
private cachePath?: Path2D; private cachePath?: Path2D;
protected pathDirty: boolean = true; protected pathDirty: boolean = false;
/** /**
* *
@ -142,7 +140,6 @@ export abstract class GraphicItemBase
} }
const path = this.cachePath; const path = this.cachePath;
if (!path) return; if (!path) return;
switch (this.mode) { switch (this.mode) {
case GraphicMode.Fill: case GraphicMode.Fill:
ctx.fill(path, this.fillRule); ctx.fill(path, this.fillRule);
@ -175,13 +172,11 @@ export abstract class GraphicItemBase
ctx.lineCap = this.lineCap; ctx.lineCap = this.lineCap;
ctx.lineJoin = this.lineJoin; ctx.lineJoin = this.lineJoin;
ctx.setLineDash(this.lineDash); ctx.setLineDash(this.lineDash);
if (this.actionStroke) {
return ctx.isPointInStroke(path, fixX, fixY);
}
switch (this.mode) { switch (this.mode) {
case GraphicMode.Fill: case GraphicMode.Fill:
return ctx.isPointInPath(path, fixX, fixY, this.fillRule); return ctx.isPointInPath(path, fixX, fixY, this.fillRule);
case GraphicMode.Stroke: case GraphicMode.Stroke:
return ctx.isPointInStroke(path, fixX, fixY);
case GraphicMode.FillAndStroke: case GraphicMode.FillAndStroke:
case GraphicMode.StrokeAndFill: case GraphicMode.StrokeAndFill:
return ( return (
@ -356,10 +351,6 @@ export abstract class GraphicItemBase
this.miterLimit = nextValue; this.miterLimit = nextValue;
this.update(); this.update();
return true; return true;
case 'actionStroke':
if (!this.assertType(nextValue, 'boolean', key)) return false;
this.actionStroke = nextValue;
return true;
} }
return false; return false;
} }

View File

@ -6,4 +6,3 @@ export * from './hero';
export * from './layer'; export * from './layer';
export * from './misc'; export * from './misc';
export * from './viewport'; export * from './viewport';
export * from './graphics';

View File

@ -97,7 +97,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
// 画布监听 // 画布监听
const canvas = this.target.canvas; const canvas = this.target.canvas;
canvas.addEventListener('mousedown', ev => { canvas.addEventListener('mousedown', ev => {
ev.preventDefault();
const mouse = this.getMouseType(ev); const mouse = this.getMouseType(ev);
this.lastMouse = mouse; this.lastMouse = mouse;
this.captureEvent( this.captureEvent(
@ -106,13 +105,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
); );
}); });
canvas.addEventListener('mouseup', ev => { canvas.addEventListener('mouseup', ev => {
ev.preventDefault();
const event = this.createMouseAction(ev, ActionType.Up); const event = this.createMouseAction(ev, ActionType.Up);
this.captureEvent(ActionType.Up, event); this.captureEvent(ActionType.Up, event);
this.captureEvent(ActionType.Click, event); this.captureEvent(ActionType.Click, event);
}); });
canvas.addEventListener('mousemove', ev => { canvas.addEventListener('mousemove', ev => {
ev.preventDefault();
const event = this.createMouseAction( const event = this.createMouseAction(
ev, ev,
ActionType.Move, ActionType.Move,
@ -134,7 +131,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
); );
}); });
canvas.addEventListener('mouseleave', ev => { canvas.addEventListener('mouseleave', ev => {
ev.preventDefault();
this.hoveredElement.forEach(v => { this.hoveredElement.forEach(v => {
v.emit('leave', this.createMouseActionBase(ev, v)); v.emit('leave', this.createMouseActionBase(ev, v));
}); });
@ -142,13 +138,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
this.beforeHovered.clear(); this.beforeHovered.clear();
}); });
document.addEventListener('touchstart', ev => { document.addEventListener('touchstart', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Down).forEach(v => { this.createTouchAction(ev, ActionType.Down).forEach(v => {
this.captureEvent(ActionType.Down, v); this.captureEvent(ActionType.Down, v);
}); });
}); });
document.addEventListener('touchend', ev => { document.addEventListener('touchend', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Up).forEach(v => { this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v); this.captureEvent(ActionType.Up, v);
this.captureEvent(ActionType.Click, v); this.captureEvent(ActionType.Click, v);
@ -156,14 +150,12 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}); });
}); });
document.addEventListener('touchcancel', ev => { document.addEventListener('touchcancel', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Up).forEach(v => { this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v); this.captureEvent(ActionType.Up, v);
this.touchInfo.delete(v.identifier); this.touchInfo.delete(v.identifier);
}); });
}); });
document.addEventListener('touchmove', ev => { document.addEventListener('touchmove', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Move).forEach(v => { this.createTouchAction(ev, ActionType.Move).forEach(v => {
const touch = this.touchInfo.get(v.identifier); const touch = this.touchInfo.get(v.identifier);
if (!touch) return; if (!touch) return;
@ -180,7 +172,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}); });
}); });
canvas.addEventListener('wheel', ev => { canvas.addEventListener('wheel', ev => {
ev.preventDefault();
this.captureEvent( this.captureEvent(
ActionType.Wheel, ActionType.Wheel,
this.createWheelAction(ev, ActionType.Wheel) this.createWheelAction(ev, ActionType.Wheel)

View File

@ -92,7 +92,6 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
nextSibling: function ( nextSibling: function (
node: RenderItem<ERenderItemEvent> node: RenderItem<ERenderItemEvent>
): RenderItem<ERenderItemEvent> | null { ): RenderItem<ERenderItemEvent> | null {
if (!node) return null;
if (!node.parent) { if (!node.parent) {
return null; return null;
} else { } else {

View File

@ -78,8 +78,6 @@ export interface BaseProps {
scale?: ElementScale; scale?: ElementScale;
/** 旋转属性,单位弧度,是 transform 的简写属性之一 */ /** 旋转属性,单位弧度,是 transform 的简写属性之一 */
rotate?: number; rotate?: number;
/** 这个元素是否不会触发任何交互事件cursor 属性也会无效),当执行到此元素时,会下穿至下一个元素 */
noevent?: boolean;
} }
export interface SpriteProps extends BaseProps { export interface SpriteProps extends BaseProps {
@ -157,8 +155,6 @@ export interface GraphicPropsBase extends BaseProps, Partial<ILineProperty> {
fillStyle?: CanvasStyle; fillStyle?: CanvasStyle;
/** 描边样式 */ /** 描边样式 */
strokeStyle?: CanvasStyle; strokeStyle?: CanvasStyle;
/** 在交互时,是否只检查交互位置只在描边上,对 fill, stroke, strokeAndFill 均有效 */
actionStroke?: boolean;
} }
export interface RectProps extends GraphicPropsBase {} export interface RectProps extends GraphicPropsBase {}
@ -233,7 +229,7 @@ export interface RectRProps extends GraphicPropsBase {
*/ */
circle?: RectRCircleParams; circle?: RectRCircleParams;
/** /**
* `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]` * `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]`
* *
* - 1 `[rx1, ry1]` * - 1 `[rx1, ry1]`
* - 2 `[rx1, ry1]` `[rx2, ry2]` * - 2 `[rx1, ry1]` `[rx2, ry2]`

View File

@ -1,7 +1,6 @@
import { backDir, has } from '@/plugin/game/utils'; import { backDir, has } from '@/plugin/game/utils';
import { loading } from '../game'; import { loading } from '../game';
import type { LayerDoorAnimate } from '@/core/render/preset/floor'; import type { LayerDoorAnimate } from '@/core/render/preset/floor';
import { getSkillLevel } from '@/plugin/game/skillTree';
/** /**
* *
@ -63,24 +62,6 @@ export namespace HeroSkill {
export const Shield = Skill.Shield; export const Shield = Skill.Shield;
export const Jump = Skill.Jump; 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 { interface SkillSave {
autoSkill: boolean; autoSkill: boolean;
learned: Skill[]; learned: Skill[];
@ -90,29 +71,6 @@ export namespace HeroSkill {
let autoSkill = true; let autoSkill = true;
let enabled: Skill = Skill.None; 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) { export function setAutoSkill(auto: boolean) {
autoSkill = auto; autoSkill = auto;
} }

View File

@ -1,503 +0,0 @@
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);

View File

@ -12,8 +12,6 @@ interface ProgressProps extends DefaultProps {
success?: CanvasStyle; success?: CanvasStyle;
/** 未完成部分的样式默认为灰色gray */ /** 未完成部分的样式默认为灰色gray */
background?: CanvasStyle; background?: CanvasStyle;
/** 线宽度 */
lineWidth?: number;
} }
const progressProps = { const progressProps = {
@ -27,22 +25,10 @@ export const Progress = defineComponent<ProgressProps>(props => {
const { ctx } = canvas; const { ctx } = canvas;
const width = props.loc[2] ?? 200; const width = props.loc[2] ?? 200;
const height = props.loc[3] ?? 200; const height = props.loc[3] ?? 200;
ctx.lineCap = 'round'; ctx.fillStyle = props.background ?? 'gray';
const lineWidth = props.lineWidth ?? 2; ctx.fillRect(0, 0, width, height);
ctx.lineWidth = lineWidth; ctx.fillStyle = props.success ?? 'green';
ctx.strokeStyle = props.background ?? 'gray'; ctx.fillRect(0, 0, width * props.progress, height);
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, () => { watch(props, () => {

View File

@ -211,7 +211,7 @@ const textboxOptions = {
'hidden', 'hidden',
'anchorX', 'anchorX',
'anchorY', 'anchorY',
'anti', 'antiAliasing',
'cache', 'cache',
'composite', 'composite',
'fall', 'fall',

View File

@ -33,7 +33,6 @@ import {
RightStatusBar RightStatusBar
} from './statusBar'; } from './statusBar';
import { onLoaded } from '../use'; import { onLoaded } from '../use';
import { ReplayingStatus } from './toolbar';
const MainScene = defineComponent(() => { const MainScene = defineComponent(() => {
const layerGroupExtends: ILayerGroupRenderExtends[] = [ const layerGroupExtends: ILayerGroupRenderExtends[] = [
@ -71,7 +70,6 @@ const MainScene = defineComponent(() => {
}; };
const map = ref<LayerGroup>(); const map = ref<LayerGroup>();
const hideStatus = ref(false);
const weather = new WeatherController('main'); const weather = new WeatherController('main');
onMounted(() => { onMounted(() => {
@ -94,31 +92,12 @@ const MainScene = defineComponent(() => {
exAtk: 0, exAtk: 0,
magicDef: 0 magicDef: 0
}); });
const replayStatus: ReplayingStatus = reactive({ const rightStatus: IRightHeroStatus = 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 { getHeroStatusOn } = Mota.requireAll('fn');
const updateStatus = () => { const updateStatus = () => {
if (!core.status || !core.status.hero || !core.status.floorId) return;
hideStatus.value = core.getFlag('hideStatusBar', false);
const hero = core.status.hero; const hero = core.status.hero;
const floor = core.status.floorId;
leftStatus.atk = getHeroStatusOn('atk'); leftStatus.atk = getHeroStatusOn('atk');
leftStatus.hp = getHeroStatusOn('hp'); leftStatus.hp = getHeroStatusOn('hp');
leftStatus.def = getHeroStatusOn('def'); leftStatus.def = getHeroStatusOn('def');
@ -133,32 +112,6 @@ const MainScene = defineComponent(() => {
leftStatus.regen = getHeroStatusOn('hpmax'); leftStatus.regen = getHeroStatusOn('hpmax');
leftStatus.exAtk = getHeroStatusOn('mana'); leftStatus.exAtk = getHeroStatusOn('mana');
leftStatus.magicDef = getHeroStatusOn('magicDef'); 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); const loaded = ref(false);
@ -174,10 +127,8 @@ const MainScene = defineComponent(() => {
<LeftStatusBar <LeftStatusBar
loc={[0, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]} loc={[0, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
status={leftStatus} status={leftStatus}
hidden={hideStatus.value}
></LeftStatusBar> ></LeftStatusBar>
)} )}
<g-line line={[180, 0, 180, 480]} lineWidth={1} />
<container id="map-draw" {...mapDrawProps} x={180} zIndex={10}> <container id="map-draw" {...mapDrawProps} x={180} zIndex={10}>
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}> <layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
<layer layer="bg" zIndex={10}></layer> <layer layer="bg" zIndex={10}></layer>
@ -190,12 +141,10 @@ const MainScene = defineComponent(() => {
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox> <Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
<FloorChange id="floor-change" zIndex={50}></FloorChange> <FloorChange id="floor-change" zIndex={50}></FloorChange>
</container> </container>
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
{loaded.value && ( {loaded.value && (
<RightStatusBar <RightStatusBar
loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]} loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
status={rightStatus} status={rightStatus}
hidden={hideStatus.value}
></RightStatusBar> ></RightStatusBar>
)} )}
<container <container
@ -204,23 +153,6 @@ const MainScene = defineComponent(() => {
> >
{mainUIController.render()} {mainUIController.render()}
</container> </container>
<g-rect
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
hidden={hideStatus.value}
zIndex={100}
stroke
noevent
></g-rect>
<g-line
line={[180, 0, 480 + 180, 0]}
hidden={!hideStatus.value}
zIndex={100}
/>
<g-line
line={[180, 480, 480 + 180, 480]}
hidden={!hideStatus.value}
zIndex={100}
/>
</container> </container>
); );
}); });

View File

@ -1,18 +1,8 @@
import { GameUI } from '@/core/system'; import { GameUI } from '@/core/system';
import { computed, defineComponent, ref, watch } from 'vue'; import { defineComponent } from 'vue';
import { SetupComponentOptions, TextContent } from '../components'; import { SetupComponentOptions } from '../components';
import { DefaultProps, ElementLocator, Sprite } from '@/core/render'; import { ElementLocator } from '@/core/render';
import { transitionedColor } from '../use';
import { linear } from 'mutate-animate';
import { Scroll } from '../components/scroll'; 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 { export interface ILeftHeroStatus {
hp: number; hp: number;
@ -34,14 +24,15 @@ export interface ILeftHeroStatus {
magicDef: number; magicDef: number;
} }
interface StatusBarProps<T> extends DefaultProps { export interface IRightHeroStatus {}
interface StatusBarProps<T> {
loc: ElementLocator; loc: ElementLocator;
status: T; status: T;
hidden: boolean;
} }
const statusBarProps = { const statusBarProps = {
props: ['loc', 'status', 'hidden'] props: ['loc', 'status']
} satisfies SetupComponentOptions<StatusBarProps<unknown>>; } satisfies SetupComponentOptions<StatusBarProps<unknown>>;
export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>( export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
@ -56,7 +47,7 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
const s = p.status; const s = p.status;
const f = core.formatBigNumber; const f = core.formatBigNumber;
const floorName = computed(() => core.floors[s.floor]?.title ?? ''); const floorName = core.floors[s.floor].title;
const key = (num: number) => { const key = (num: number) => {
return num.toString().padStart(2, '0'); return num.toString().padStart(2, '0');
@ -94,9 +85,10 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
return () => { return () => {
return ( return (
<container loc={p.loc} hidden={p.hidden}> <container loc={p.loc}>
<g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
<text <text
text={floorName.value} text={floorName}
loc={central(24)} loc={central(24)}
font={font1} font={font1}
cursor="pointer" cursor="pointer"
@ -171,228 +163,13 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
statusBarProps 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>>( export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
p => { 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 () => {
return ( return (
<container loc={p.loc} hidden={p.hidden}> <container loc={p.loc}>
<g-rectr <g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
loc={[10, 10, 160, 24]} <Scroll loc={[0, 0, 180, 100]}></Scroll>
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> </container>
); );
}; };

View File

@ -1,364 +0,0 @@
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);

View File

@ -3,6 +3,7 @@
export function init() { export function init() {
if (main.mode === 'editor') return; if (main.mode === 'editor') return;
const { mainUi, fixedUi, mainSetting } = Mota.requireAll('var'); const { mainUi, fixedUi, mainSetting } = Mota.requireAll('var');
const CustomToolbar = Mota.require('class', 'CustomToolbar');
ui.prototype.drawBook = function () { ui.prototype.drawBook = function () {
if (!core.isReplaying()) return mainUi.open('book'); if (!core.isReplaying()) return mainUi.open('book');
@ -27,30 +28,48 @@ export function init() {
if (!core.control.noAutoEvents) core.checkAutoEvents(); if (!core.control.noAutoEvents) core.checkAutoEvents();
core.control._updateStatusBar_setToolboxIcon(); core.control._updateStatusBar_setToolboxIcon();
core.control.noAutoEvents = true; core.control.noAutoEvents = true;
// 更新vue状态栏
updateVueStatusBar();
Mota.require('var', 'hook').emit('statusBarUpdate'); Mota.require('var', 'hook').emit('statusBarUpdate');
}; };
// todo: 多个状态栏分离与控制
control.prototype.showStatusBar = function () { control.prototype.showStatusBar = function () {
if (main.mode == 'editor') return; if (main.mode == 'editor') return;
const defaultsTool = CustomToolbar.get('@defaults');
core.removeFlag('hideStatusBar'); core.removeFlag('hideStatusBar');
if (!fixedUi.hasName('statusBar')) {
fixedUi.open('statusBar');
}
defaultsTool?.show();
if (mainSetting.getValue('ui.tips')) { if (mainSetting.getValue('ui.tips')) {
if (!fixedUi.hasName('tips')) { if (!fixedUi.hasName('tips')) {
fixedUi.open('tips'); fixedUi.open('tips');
} }
} }
core.updateStatusBar();
}; };
control.prototype.hideStatusBar = function (showToolbox) { control.prototype.hideStatusBar = function (showToolbox) {
if (main.mode == 'editor') return; if (main.mode == 'editor') return;
const defaultsTool = CustomToolbar.get('@defaults');
// 如果原本就是隐藏的,则先显示 // 如果原本就是隐藏的,则先显示
if (!core.domStyle.showStatusBar) this.showStatusBar(); if (!core.domStyle.showStatusBar) this.showStatusBar();
if (core.isReplaying()) showToolbox = true; if (core.isReplaying()) showToolbox = true;
fixedUi.closeByName('statusBar');
if (!showToolbox) {
defaultsTool?.closeAll();
}
fixedUi.closeByName('tips'); fixedUi.closeByName('tips');
core.setFlag('hideStatusBar', true); core.setFlag('hideStatusBar', true);
core.setFlag('showToolbox', showToolbox || null); core.setFlag('showToolbox', showToolbox || null);
core.updateStatusBar();
}; };
} }
function updateVueStatusBar() {
Mota.r(() => {
const status = Mota.require('var', 'status');
status.value = !status.value;
});
}

View File

@ -392,7 +392,7 @@ export function generateBinary(arr: boolean[]) {
let num = 0; let num = 0;
arr.forEach((v, i) => { arr.forEach((v, i) => {
if (v) { if (v) {
num |= 1 << i; num += 1 << i;
} }
}); });
return num; return num;

View File

@ -53,7 +53,7 @@
<div id="tool-preview-container"> <div id="tool-preview-container">
<div class="tool-preview-item" v-for="item of bar.items"> <div class="tool-preview-item" v-for="item of bar.items">
<component <component
:is="CustomToolbar.info[item.type].show as any" :is="(CustomToolbar.info[item.type].show as any)"
:item="item" :item="item"
:toolbar="bar" :toolbar="bar"
></component> ></component>
@ -179,7 +179,7 @@
<div id="tool-preview-container"> <div id="tool-preview-container">
<div class="tool-preview-item" v-for="item of bar.items"> <div class="tool-preview-item" v-for="item of bar.items">
<component <component
:is="CustomToolbar.info[item.type].show as any" :is="(CustomToolbar.info[item.type].show as any)"
:item="item" :item="item"
:toolbar="bar" :toolbar="bar"
></component> ></component>
@ -292,6 +292,10 @@ function exit() {
} }
function deleteTool(id: string) { function deleteTool(id: string) {
if (id === '@defaults') {
tip('warn', '该工具栏不能删除!');
return;
}
Modal.confirm({ Modal.confirm({
title: '确定要删除这个自定义工具栏吗?', title: '确定要删除这个自定义工具栏吗?',
onOk() { onOk() {