feat: 跟进 2.A 的更改

This commit is contained in:
unanmed 2024-04-20 12:27:38 +08:00
parent 0dfb7e4b99
commit 86e6e76286
38 changed files with 851 additions and 493 deletions

View File

@ -8,4 +8,5 @@ public/project/maps.js
public/_server/**/*.js public/_server/**/*.js
script/**/*.js script/**/*.js
public/editor.html public/editor.html
keyCodes.ts keyCodes.ts
src/core/main/setting.ts

2
components.d.ts vendored
View File

@ -10,7 +10,6 @@ declare module '@vue/runtime-core' {
AButton: typeof import('ant-design-vue/es')['Button'] AButton: typeof import('ant-design-vue/es')['Button']
ADivider: typeof import('ant-design-vue/es')['Divider'] ADivider: typeof import('ant-design-vue/es')['Divider']
AInput: typeof import('ant-design-vue/es')['Input'] AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AProgress: typeof import('ant-design-vue/es')['Progress'] AProgress: typeof import('ant-design-vue/es')['Progress']
ASelect: typeof import('ant-design-vue/es')['Select'] ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
@ -18,7 +17,6 @@ declare module '@vue/runtime-core' {
ASwitch: typeof import('ant-design-vue/es')['Switch'] ASwitch: typeof import('ant-design-vue/es')['Switch']
Box: typeof import('./src/components/box.vue')['default'] Box: typeof import('./src/components/box.vue')['default']
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default'] BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
Changable: typeof import('./src/components/changable.vue')['default']
Colomn: typeof import('./src/components/colomn.vue')['default'] Colomn: typeof import('./src/components/colomn.vue')['default']
EnemyOne: typeof import('./src/components/enemyOne.vue')['default'] EnemyOne: typeof import('./src/components/enemyOne.vue')['default']
Scroll: typeof import('./src/components/scroll.vue')['default'] Scroll: typeof import('./src/components/scroll.vue')['default']

View File

@ -602,6 +602,7 @@ core.prototype._afterLoadResources = function (callback) {
// if (core.plugin._afterLoadResources) core.plugin._afterLoadResources(); // if (core.plugin._afterLoadResources) core.plugin._afterLoadResources();
core.showStartAnimate(); core.showStartAnimate();
Mota.require('var', 'hook').emit('load');
if (callback) callback(); if (callback) callback();
}; };

View File

@ -4421,24 +4421,31 @@ events.prototype._checkLvUp_check = function () {
}; };
////// 尝试使用道具 ////// ////// 尝试使用道具 //////
events.prototype.tryUseItem = function (itemId) { events.prototype.tryUseItem = function (itemId, noRoute, callback) {
if (itemId == 'book') { if (itemId == 'book') {
core.ui.closePanel(); core.ui.closePanel();
return core.openBook(false); core.openBook(false);
callback();
return;
} }
if (itemId == 'fly') { if (itemId == 'fly') {
core.ui.closePanel(); core.ui.closePanel();
return core.useFly(false); core.useFly(false);
callback();
return;
} }
if (itemId == 'centerFly') { if (itemId == 'centerFly') {
core.ui.closePanel(); core.ui.closePanel();
return core.ui._drawCenterFly(); core.ui._drawCenterFly();
callback();
return;
} }
if (core.canUseItem(itemId)) { if (core.canUseItem(itemId)) {
core.ui.closePanel(); core.ui.closePanel();
core.useItem(itemId); core.useItem(itemId, noRoute, callback);
} else { } else {
core.playSound('操作失败'); core.playSound('操作失败');
core.drawTip('当前无法使用' + core.material.items[itemId].name, itemId); core.drawTip('当前无法使用' + core.material.items[itemId].name, itemId);
} }
callback();
}; };

View File

@ -48,6 +48,7 @@ function show(index: number) {
position: fixed; position: fixed;
overflow: visible; overflow: visible;
display: block; display: block;
font-size: 80%;
font-family: 'normal'; font-family: 'normal';
} }
@ -61,7 +62,6 @@ function show(index: number) {
top: 0; top: 0;
position: fixed; position: fixed;
background-color: #000b; background-color: #000b;
backdrop-filter: blur(5px);
z-index: 1; z-index: 1;
} }

View File

@ -43,7 +43,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, ref, useSlots, watch } from 'vue'; import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
import { DragOutlined } from '@ant-design/icons-vue'; import { DragOutlined } from '@ant-design/icons-vue';
import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use'; import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use';
import { has } from '../plugin/utils'; import { has } from '../plugin/utils';
@ -231,7 +231,6 @@ onUnmounted(() => {
top: 50px; top: 50px;
display: flex; display: flex;
overflow: visible; overflow: visible;
font-family: 'normal';
} }
.box-main { .box-main {

View File

@ -19,11 +19,14 @@
class="special-text" class="special-text"
v-if="has(enemy.special) && enemy.special.length > 0" v-if="has(enemy.special) && enemy.special.length > 0"
> >
<span <template v-for="(text, i) in enemy.showSpecial">
v-for="(text, i) in enemy.showSpecial" <span
:style="{ color: text[2] }" v-if="i < (isMobile ? 1 : 2)"
>&nbsp;{{ text[0] }}&nbsp;</span :style="{ color: text[2] }"
> >&nbsp;{{ text[0] }}&nbsp;</span
>
<span v-if="i === (isMobile ? 1 : 2)">...</span>
</template>
</div> </div>
<div class="special-text" v-else>无属性</div> <div class="special-text" v-else>无属性</div>
</div> </div>
@ -220,12 +223,12 @@ function enter() {
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.rightbar { .rightbar {
width: 80%; width: 80%;
font-size: 85%; font-size: 110%;
} }
.leftbar { .leftbar {
width: 20%; width: 20%;
font-size: 80%; font-size: 100%;
} }
.enemy-container { .enemy-container {

View File

@ -80,8 +80,8 @@ export class BgmController
this.playing = true; this.playing = true;
if (!this.disable) { if (!this.disable) {
this.setTransitionAnimate(id, 1); this.setTransitionAnimate(id, 1, when);
if (this.now) this.setTransitionAnimate(this.now, 0, when); if (this.now) this.setTransitionAnimate(this.now, 0);
} }
if (!noStack) { if (!noStack) {

View File

@ -1,6 +1,5 @@
import { has } from '@/plugin/utils'; import { has } from '@/plugin/utils';
import { AudioParamOf, AudioPlayer } from './audio'; import { AudioParamOf, AudioPlayer } from './audio';
import resource from '@/data/resource.json';
import { ResourceController } from '../loader/controller'; import { ResourceController } from '../loader/controller';
// todo: 立体声,可设置音源位置 // todo: 立体声,可设置音源位置
@ -24,7 +23,6 @@ export class SoundEffect extends AudioPlayer {
gain: GainNode = AudioPlayer.ac.createGain(); gain: GainNode = AudioPlayer.ac.createGain();
panner: PannerNode | null = null; panner: PannerNode | null = null;
merger: ChannelMergerNode | null = null;
set volumn(value: number) { set volumn(value: number) {
this.gain.gain.value = value * SoundEffect.volume; this.gain.gain.value = value * SoundEffect.volume;
@ -63,9 +61,7 @@ export class SoundEffect extends AudioPlayer {
* 线 * 线
* ```txt * ```txt
* source -> gain -> destination * source -> gain -> destination
* source -> panner -> gain --> destination * source -> panner -> gain -> destination
* source -> merger -> panner -> gain -> destination
*
* ``` * ```
* @param stereo * @param stereo
*/ */
@ -74,20 +70,10 @@ export class SoundEffect extends AudioPlayer {
const ac = AudioPlayer.ac; const ac = AudioPlayer.ac;
if (!channel) return; if (!channel) return;
this.panner = null; this.panner = null;
this.merger = null;
if (stereo) { if (stereo) {
this.panner = ac.createPanner(); this.panner = ac.createPanner();
this.panner.connect(this.gain); this.panner.connect(this.gain);
if (channel === 1) { this.baseNode = [{ node: this.panner }];
this.merger = ac.createChannelMerger();
this.merger.connect(this.panner);
this.baseNode = [
{ node: this.merger, channel: 0 },
{ node: this.merger, channel: 1 }
];
} else {
this.baseNode = [{ node: this.panner }];
}
} else { } else {
this.baseNode = [{ node: this.gain }]; this.baseNode = [{ node: this.gain }];
} }
@ -136,15 +122,18 @@ export class SoundEffect extends AudioPlayer {
* @param source * @param source
* @param listener * @param listener
*/ */
setPanner(source: Partial<Panner>, listener: Partial<Listener>) { setPanner(source?: Partial<Panner>, listener?: Partial<Listener>) {
if (!this.panner) return; if (!this.panner) return;
console.log(2); if (source) {
for (const [key, value] of Object.entries(source)) { for (const [key, value] of Object.entries(source)) {
this.panner[key as keyof Panner].value = value; this.panner[key as keyof Panner].value = value;
}
} }
const l = AudioPlayer.ac.listener; if (listener) {
for (const [key, value] of Object.entries(listener)) { const l = AudioPlayer.ac.listener;
l[key as keyof Listener].value = value; for (const [key, value] of Object.entries(listener)) {
l[key as keyof Listener].value = value;
}
} }
} }
} }
@ -161,7 +150,6 @@ export class SoundController extends ResourceController<
* @param data ArrayBuffer信息AudioBuffer * @param data ArrayBuffer信息AudioBuffer
*/ */
add(uri: string, data: ArrayBuffer) { add(uri: string, data: ArrayBuffer) {
const stereo = resource.stereoSE.includes(uri);
const se = new SoundEffect(data, true); const se = new SoundEffect(data, true);
if (this.list[uri]) { if (this.list[uri]) {
console.warn(`Repeated sound effect: '${uri}'.`); console.warn(`Repeated sound effect: '${uri}'.`);
@ -176,6 +164,7 @@ export class SoundController extends ResourceController<
*/ */
play(sound: SoundIds, end?: () => void): number { play(sound: SoundIds, end?: () => void): number {
const se = this.get(sound); const se = this.get(sound);
if (!se) return -1;
const index = se.playSE(); const index = se.playSE();
if (!has(index)) return -1; if (!has(index)) return -1;
this.seIndex[index] = se; this.seIndex[index] = se;

View File

@ -1,13 +1,5 @@
import { BgmController, bgm } from './audio/bgm'; import { BgmController, bgm } from './audio/bgm';
import { SoundController, SoundEffect, sound } from './audio/sound'; import { SoundController, SoundEffect, sound } from './audio/sound';
import { readyAllResource } from './loader/load';
import {
Resource,
ResourceStore,
ZippedResource,
resource,
zipResource
} from './loader/resource';
import { Focus, GameUi, UiController } from './main/custom/ui'; import { Focus, GameUi, UiController } from './main/custom/ui';
import { GameStorage } from './main/storage'; import { GameStorage } from './main/storage';
import './main/init/'; import './main/init/';
@ -20,19 +12,51 @@ import {
mainSetting, mainSetting,
settingStorage settingStorage
} from './main/setting'; } from './main/setting';
import { KeyCode } from '@/plugin/keyCodes'; import { KeyCode, ScanCode } from '@/plugin/keyCodes';
import { status } from '@/plugin/ui/statusBar'; import { status } from '@/plugin/ui/statusBar';
import './plugin'; import './plugin';
import './package'; import './package';
import { AudioPlayer } from './audio/audio'; import { AudioPlayer } from './audio/audio';
import { CustomToolbar } from './main/custom/toolbar'; import { CustomToolbar } from './main/custom/toolbar';
import { Hotkey } from './main/custom/hotkey'; import {
import { Keyboard } from './main/custom/keyboard'; Hotkey,
checkAssist,
isAssist,
unwarpBinary
} from './main/custom/hotkey';
import { Keyboard, generateKeyboardEvent } from './main/custom/keyboard';
import './main/layout'; import './main/layout';
import { MComponent, m } from './main/layout';
function ready() { import { createSettingComponents } from './main/init/settings';
readyAllResource(); import {
} createToolbarComponents,
createToolbarEditorComponents
} from './main/init/toolbar';
import { VirtualKey } from './main/init/misc';
import * as utils from '@/plugin/utils';
import * as use from '@/plugin/use';
import * as mark from '@/plugin/mark';
import * as keyCodes from '@/plugin/keyCodes';
import { addAnimate, removeAnimate } from '@/plugin/animateController';
import * as bookTools from '@/plugin/ui/book';
import * as commonTools from '@/plugin/ui/common';
import * as equipboxTools from '@/plugin/ui/equipbox';
import * as fixedTools from '@/plugin/ui/fixed';
import * as flyTools from '@/plugin/ui/fly';
import * as statusBarTools from '@/plugin/ui/statusBar';
import * as toolboxTools from '@/plugin/ui/toolbox';
import * as UI from '@ui/index';
import Box from '@/components/box.vue';
import BoxAnimate from '@/components/boxAnimate.vue';
import Colomn from '@/components/colomn.vue';
import EnemyOne from '@/components/enemyOne.vue';
import Scroll from '@/components/scroll.vue';
import EnemyCritical from '@/panel/enemyCritical.vue';
import EnemySpecial from '@/panel/enemySpecial.vue';
import EnemyTarget from '@/panel/enemyTarget.vue';
import KeyboardPanel from '@/panel/keyboard.vue';
import { MCGenerator } from './main/layout';
import { ResourceController } from './loader/controller';
// ----- 类注册 // ----- 类注册
Mota.register('class', 'AudioPlayer', AudioPlayer); Mota.register('class', 'AudioPlayer', AudioPlayer);
@ -44,15 +68,20 @@ Mota.register('class', 'GameUi', GameUi);
Mota.register('class', 'Hotkey', Hotkey); Mota.register('class', 'Hotkey', Hotkey);
Mota.register('class', 'Keyboard', Keyboard); Mota.register('class', 'Keyboard', Keyboard);
Mota.register('class', 'MotaSetting', MotaSetting); Mota.register('class', 'MotaSetting', MotaSetting);
Mota.register('class', 'Resource', Resource);
Mota.register('class', 'ResourceStore', ResourceStore);
Mota.register('class', 'SettingDisplayer', SettingDisplayer); Mota.register('class', 'SettingDisplayer', SettingDisplayer);
Mota.register('class', 'SoundController', SoundController); Mota.register('class', 'SoundController', SoundController);
Mota.register('class', 'SoundEffect', SoundEffect); Mota.register('class', 'SoundEffect', SoundEffect);
Mota.register('class', 'UiController', UiController); Mota.register('class', 'UiController', UiController);
Mota.register('class', 'ZippedResource', ZippedResource); Mota.register('class', 'MComponent', MComponent);
Mota.register('class', 'ResourceController', ResourceController);
// ----- 函数注册 // ----- 函数注册
Mota.register('fn', 'm', m);
Mota.register('fn', 'unwrapBinary', unwarpBinary);
Mota.register('fn', 'checkAssist', checkAssist);
Mota.register('fn', 'isAssist', isAssist);
Mota.register('fn', 'generateKeyboardEvent', generateKeyboardEvent);
Mota.register('fn', 'addAnimate', addAnimate);
Mota.register('fn', 'removeAnimate', removeAnimate);
// ----- 变量注册 // ----- 变量注册
Mota.register('var', 'mainUi', mainUi); Mota.register('var', 'mainUi', mainUi);
Mota.register('var', 'fixedUi', fixedUi); Mota.register('var', 'fixedUi', fixedUi);
@ -61,11 +90,43 @@ Mota.register('var', 'sound', sound);
Mota.register('var', 'gameKey', gameKey); Mota.register('var', 'gameKey', gameKey);
Mota.register('var', 'mainSetting', mainSetting); Mota.register('var', 'mainSetting', mainSetting);
Mota.register('var', 'KeyCode', KeyCode); Mota.register('var', 'KeyCode', KeyCode);
Mota.register('var', 'resource', resource);
Mota.register('var', 'zipResource', zipResource);
Mota.register('var', 'settingStorage', settingStorage); Mota.register('var', 'settingStorage', settingStorage);
Mota.register('var', 'status', status); Mota.register('var', 'status', status);
// ----- 模块注册 // ----- 模块注册
Mota.register('module', 'CustomComponents', {
createSettingComponents,
createToolbarComponents,
createToolbarEditorComponents
});
Mota.register('module', 'MiscComponents', {
VirtualKey
});
Mota.register('module', 'RenderUtils', utils);
Mota.register('module', 'Use', use);
Mota.register('module', 'Mark', mark);
Mota.register('module', 'KeyCodes', keyCodes);
Mota.register('module', 'UITools', {
book: bookTools,
common: commonTools,
equipbox: equipboxTools,
fixed: fixedTools,
fly: flyTools,
statusBar: statusBarTools,
toolbox: toolboxTools
});
Mota.register('module', 'UI', UI);
Mota.register('module', 'UIComponents', {
Box,
BoxAnimate,
Colomn,
EnemyOne,
Scroll,
EnemyCritical,
EnemySpecial,
EnemyTarget,
Keyboard: KeyboardPanel
});
Mota.register('module', 'MCGenerator', MCGenerator);
ready(); main.renderLoaded = true;
Mota.require('var', 'hook').emit('renderLoaded');

View File

@ -137,7 +137,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
* 退 * 退
* @param symbol symbol * @param symbol symbol
*/ */
dispose(symbol: symbol) { dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()) {
for (const key of Object.values(this.data)) { for (const key of Object.values(this.data)) {
key.func.delete(symbol); key.func.delete(symbol);
} }

View File

@ -116,6 +116,14 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
constructor(id: string) { constructor(id: string) {
super(); super();
this.id = id; this.id = id;
// 按比例设置初始大小
const setting = Mota.require('var', 'mainSetting');
const scale = setting.getValue('ui.toolbarScale', 100) / 100;
this.width *= scale;
this.height *= scale;
this.x *= scale;
this.y *= scale;
this.show(); this.show();
CustomToolbar.list.push(this); CustomToolbar.list.push(this);
} }
@ -188,22 +196,31 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
/** /**
* *
*/ */
refresh() { refresh(reopen: boolean = false) {
const items = this.items.splice(0); if (reopen && this.showIds.length > 0) {
nextTick(() => { this.closeAll();
this.items.push(...items); nextTick(() => {
}); this.show();
});
} else {
const items = this.items.splice(0);
nextTick(() => {
this.items.push(...items);
});
}
return this; return this;
} }
setPos(x?: number, y?: number) { setPos(x?: number, y?: number) {
has(x) && (this.x = x); has(x) && (this.x = x);
has(y) && (this.y = y); has(y) && (this.y = y);
this.emit('posChange', this);
} }
setSize(width?: number, height?: number) { setSize(width?: number, height?: number) {
has(width) && (this.width = width); has(width) && (this.width = width);
has(height) && (this.height = height); has(height) && (this.height = height);
this.emit('posChange', this);
} }
/** /**
@ -269,12 +286,14 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
static save() { static save() {
toolbarStorage.clear(); toolbarStorage.clear();
const setting = Mota.require('var', 'mainSetting');
const scale = setting.getValue('ui.toolbarScale', 100) / 100;
this.list.forEach(v => { this.list.forEach(v => {
const toSave: ToolbarSaveData = { const toSave: ToolbarSaveData = {
x: v.x, x: v.x,
y: v.y, y: v.y,
w: v.width, w: v.width / scale,
h: v.height, h: v.height / scale,
items: [] items: []
}; };
v.items.forEach(v => { v.items.forEach(v => {
@ -288,7 +307,7 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
static load() { static load() {
toolbarStorage.read(); toolbarStorage.read();
for (const [key, value] of Object.entries(toolbarStorage.data)) { for (const [key, value] of Object.entries(toolbarStorage.data)) {
const bar = new CustomToolbar(key); const bar = this.get(key) ?? new CustomToolbar(key);
bar.x = value.x; bar.x = value.x;
bar.y = value.y; bar.y = value.y;
bar.width = value.w; bar.width = value.w;
@ -299,6 +318,10 @@ export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
} }
} }
static refreshAll(reopen: boolean = false): void {
CustomToolbar.list.forEach(v => v.refresh(reopen));
}
static showAll(): number[] { static showAll(): number[] {
return CustomToolbar.list.map(v => v.show()); return CustomToolbar.list.map(v => v.show());
} }
@ -376,16 +399,16 @@ CustomToolbar.register(
} }
); );
window.addEventListener('beforeunload', () => {
CustomToolbar.save();
});
window.addEventListener('blur', () => {
CustomToolbar.save();
});
Mota.require('var', 'loading').once('coreInit', () => { Mota.require('var', 'loading').once('coreInit', () => {
CustomToolbar.load(); CustomToolbar.load();
CustomToolbar.closeAll(); CustomToolbar.closeAll();
window.addEventListener('beforeunload', e => {
CustomToolbar.save();
});
window.addEventListener('blur', () => {
CustomToolbar.save();
});
}); });
Mota.require('var', 'hook').on('reset', () => { Mota.require('var', 'hook').on('reset', () => {
CustomToolbar.showAll(); CustomToolbar.showAll();

View File

@ -9,8 +9,6 @@ interface FocusEvent<T> extends EmitableEvent {
unfocus: (before: T | null) => void; unfocus: (before: T | null) => void;
add: (item: T) => void; add: (item: T) => void;
pop: (item: T | null) => void; pop: (item: T | null) => void;
register: (item: T[]) => void;
unregister: (item: T[]) => void;
splice: (spliced: T[]) => void; splice: (spliced: T[]) => void;
} }
@ -163,7 +161,7 @@ interface IndexedGameUi extends ShowableGameUi {
} }
interface HoldOnController { interface HoldOnController {
end(): void; end(noClosePanel?: boolean): void;
} }
export class UiController extends Focus<IndexedGameUi> { export class UiController extends Focus<IndexedGameUi> {
@ -182,7 +180,7 @@ export class UiController extends Focus<IndexedGameUi> {
v.ui.emit('close'); v.ui.emit('close');
}); });
if (this.stack.length === 0) { if (this.stack.length === 0) {
if (!this.hold) this.emit('end'); if (!this.hold) this.emit('end', false);
this.hold = false; this.hold = false;
} }
}); });
@ -227,8 +225,8 @@ export class UiController extends Focus<IndexedGameUi> {
this.hold = true; this.hold = true;
return { return {
end: () => { end: (noClosePanel: boolean = false) => {
this.emit('end'); this.emit('end', noClosePanel);
} }
}; };
} }

View File

@ -9,7 +9,8 @@ interface Components {
Number: SettingComponent; Number: SettingComponent;
HotkeySetting: SettingComponent; HotkeySetting: SettingComponent;
ToolbarEditor: SettingComponent; ToolbarEditor: SettingComponent;
RadioSetting: (items: string[]) => SettingComponent; Radio: (items: string[]) => SettingComponent;
Performance: SettingComponent;
} }
export type { Components as SettingDisplayComponents }; export type { Components as SettingDisplayComponents };
@ -21,7 +22,8 @@ export function createSettingComponents() {
Number: NumberSetting, Number: NumberSetting,
HotkeySetting, HotkeySetting,
ToolbarEditor, ToolbarEditor,
RadioSetting Radio: RadioSetting,
Performance: PerformanceSetting
}; };
return com; return com;
} }
@ -63,7 +65,7 @@ function NumberSetting(props: SettingComponentProps) {
if (value < (item.step?.[0] ?? 0) || value > (item.step?.[1] ?? 100)) { if (value < (item.step?.[0] ?? 0) || value > (item.step?.[1] ?? 100)) {
return; return;
} }
setting.setValue(displayer.selectStack.join('.'), value); setting.setValue(displayer.selectStack.join('.'), Math.round(value));
displayer.update(); displayer.update();
}; };
@ -180,3 +182,18 @@ function ToolbarEditor(props: SettingComponentProps) {
</div> </div>
); );
} }
function PerformanceSetting(props: SettingComponentProps) {
return (
<div style="display: flex; justify-content: center">
<Button
style="font-size: 75%"
type="primary"
size="large"
onClick={() => showSpecialSetting('performance')}
>
</Button>
</div>
);
}

View File

@ -8,6 +8,7 @@ import { checkAssist } from '../custom/hotkey';
import { getVitualKeyOnce } from '@/plugin/utils'; import { getVitualKeyOnce } from '@/plugin/utils';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { Select, SelectOption } from 'ant-design-vue'; import { Select, SelectOption } from 'ant-design-vue';
import { mainSetting } from '../setting';
// todo: 新增更改设置的ToolItem // todo: 新增更改设置的ToolItem
@ -53,15 +54,18 @@ function KeyTool(props: CustomToolbarProps<'hotkey'>) {
function ItemTool(props: CustomToolbarProps<'item'>) { function ItemTool(props: CustomToolbarProps<'item'>) {
const { item, toolbar } = props; const { item, toolbar } = props;
const scale = mainSetting.getValue('ui.toolbarScale', 100) / 100;
return ( return (
<div <div
style="display: flex; justify-content: center; width: 50px" style={`display: flex; justify-content: center; width: ${
50 * scale
}px`}
onClick={() => toolbar.emitTool(item.id)} onClick={() => toolbar.emitTool(item.id)}
> >
<BoxAnimate <BoxAnimate
noborder={true} noborder={true}
width={50} width={50 * scale}
height={50} height={50 * scale}
id={item.item} id={item.item}
></BoxAnimate> ></BoxAnimate>
</div> </div>

View File

@ -1,6 +1,7 @@
import * as UI from '@ui/.'; import * as UI from '@ui/.';
import * as MiscUI from './misc'; import * as MiscUI from './misc';
import { GameUi, UiController } from '../custom/ui'; import { GameUi, UiController } from '../custom/ui';
import { mainSetting } from '../setting';
export const mainUi = new UiController(); export const mainUi = new UiController();
mainUi.register( mainUi.register(
@ -35,20 +36,34 @@ fixedUi.register(
); );
fixedUi.showAll(); fixedUi.showAll();
let loaded = false;
let mounted = false;
const hook = Mota.require('var', 'hook'); const hook = Mota.require('var', 'hook');
hook.once('mounted', () => { hook.once('mounted', () => {
const ui = document.getElementById('ui-main')!; const ui = document.getElementById('ui-main')!;
const fixed = document.getElementById('ui-fixed')!; const fixed = document.getElementById('ui-fixed')!;
const blur = mainSetting.getSetting('screen.blur');
mainUi.on('start', () => { mainUi.on('start', () => {
ui.style.display = 'flex'; ui.style.display = 'flex';
if (blur?.value) {
ui.style.backdropFilter = 'blur(5px)';
ui.style.backgroundColor = 'rgba(0,0,0,0.7333)';
} else {
ui.style.backdropFilter = 'none';
ui.style.backgroundColor = 'rgba(0,0,0,0.85)';
}
core.lockControl(); core.lockControl();
}); });
mainUi.on('end', () => { mainUi.on('end', noClosePanel => {
ui.style.display = 'none'; ui.style.display = 'none';
try { if (!noClosePanel) {
core.closePanel(); try {
} catch {} core.closePanel();
} catch {}
}
}); });
fixedUi.on('start', () => { fixedUi.on('start', () => {
fixed.style.display = 'block'; fixed.style.display = 'block';
@ -57,6 +72,15 @@ hook.once('mounted', () => {
fixed.style.display = 'none'; fixed.style.display = 'none';
}); });
// todo: 暂时先这么搞,之后重写加载界面,需要改成先显示加载界面,加载完毕后再打开这个界面 if (loaded && !mounted) {
fixedUi.open('start'); fixedUi.open('start');
}
mounted = true;
});
hook.once('load', () => {
if (mounted) {
// todo: 暂时先这么搞,之后重写加载界面,需要改成先显示加载界面,加载完毕后再打开这个界面
fixedUi.open('start');
}
loaded = true;
}); });

View File

@ -5,10 +5,12 @@ import {
VNode, VNode,
VNodeChild, VNodeChild,
defineComponent, defineComponent,
h, h as hVue,
isVNode,
onMounted onMounted
} from 'vue'; } from 'vue';
import BoxAnimate from '@/components/boxAnimate.vue'; import BoxAnimate from '@/components/boxAnimate.vue';
import { ensureArray } from '@/plugin/utils';
interface VForRenderer { interface VForRenderer {
type: '@v-for'; type: '@v-for';
@ -18,7 +20,7 @@ interface VForRenderer {
interface MotaComponent extends MotaComponentConfig { interface MotaComponent extends MotaComponentConfig {
type: string; type: string;
children: MComponent[] | MComponent; children: (MComponent | MotaComponent | VNode)[];
} }
interface MotaComponentConfig { interface MotaComponentConfig {
@ -32,7 +34,7 @@ interface MotaComponentConfig {
velse?: boolean; velse?: boolean;
} }
type OnSetupFunction = (props: Record<string, any>) => void; type OnSetupFunction = (props: Record<string, any>, ctx: SetupContext) => void;
type SetupFunction = ( type SetupFunction = (
props: Record<string, any>, props: Record<string, any>,
ctx: SetupContext ctx: SetupContext
@ -43,6 +45,7 @@ type RetFunction = (
) => VNodeChild | VNodeChild[]; ) => VNodeChild | VNodeChild[];
type OnMountedFunction = ( type OnMountedFunction = (
props: Record<string, any>, props: Record<string, any>,
ctx: SetupContext,
canvas: HTMLCanvasElement[] canvas: HTMLCanvasElement[]
) => void; ) => void;
@ -51,6 +54,12 @@ type NonComponentConfig = Omit<
'innerText' | 'component' | 'slots' | 'dComponent' 'innerText' | 'component' | 'slots' | 'dComponent'
>; >;
type MComponentChildren =
| (MComponent | MotaComponent | VNode)[]
| MComponent
| MotaComponent
| VNode;
export class MComponent { export class MComponent {
static mountNum: number = 0; static mountNum: number = 0;
@ -88,7 +97,7 @@ export class MComponent {
* @param children * @param children
* @param config {@link MComponent.h} * @param config {@link MComponent.h}
*/ */
div(children?: MComponent[] | MComponent, config?: NonComponentConfig) { div(children?: MComponentChildren, config?: NonComponentConfig) {
return this.h('div', children, config); return this.h('div', children, config);
} }
@ -97,7 +106,7 @@ export class MComponent {
* @param children * @param children
* @param config {@link MComponent.h} * @param config {@link MComponent.h}
*/ */
span(children?: MComponent[] | MComponent, config?: NonComponentConfig) { span(children?: MComponentChildren, config?: NonComponentConfig) {
return this.h('span', children, config); return this.h('span', children, config);
} }
@ -124,7 +133,7 @@ export class MComponent {
*/ */
com( com(
component: Component | MComponent, component: Component | MComponent,
config: Omit<MotaComponentConfig, 'innerText' | 'component'> config?: Omit<MotaComponentConfig, 'innerText' | 'component'>
) { ) {
return this.h(component, [], config); return this.h(component, [], config);
} }
@ -136,11 +145,7 @@ export class MComponent {
* MComponent.vNode函数生成 * MComponent.vNode函数生成
*/ */
vfor<T>(items: T[] | (() => T[]), map: (value: T, index: number) => VNode) { vfor<T>(items: T[] | (() => T[]), map: (value: T, index: number) => VNode) {
this.content.push({ this.content.push(MCGenerator.vfor(items, map));
type: '@v-for',
items,
map
});
return this; return this;
} }
@ -180,32 +185,10 @@ export class MComponent {
*/ */
h( h(
type: string | Component | MComponent, type: string | Component | MComponent,
children?: MComponent[] | MComponent, children?: MComponentChildren,
config: MotaComponentConfig = {} config: MotaComponentConfig = {}
): this { ): this {
if (typeof type === 'string') { this.content.push(MCGenerator.h(type, children, config));
this.content.push({
type,
children: children ?? [],
props: config.props,
innerText: config.innerText,
slots: config.slots,
vif: config.vif,
velse: config.velse,
component: config.component
});
} else {
this.content.push({
type: 'component',
children: children ?? [],
props: config.props,
innerText: config.innerText,
slots: config.slots,
vif: config.vif,
velse: config.velse,
component: type
});
}
return this; return this;
} }
@ -253,11 +236,12 @@ export class MComponent {
return defineComponent( return defineComponent(
(props, ctx) => { (props, ctx) => {
const mountNum = MComponent.mountNum++; const mountNum = MComponent.mountNum++;
this.onSetupFn?.(props); this.onSetupFn?.(props, ctx);
onMounted(() => { onMounted(() => {
this.onMountedFn?.( this.onMountedFn?.(
props, props,
ctx,
Array.from( Array.from(
document.getElementsByClassName( document.getElementsByClassName(
`--mota-component-canvas-${mountNum}` `--mota-component-canvas-${mountNum}`
@ -269,8 +253,6 @@ export class MComponent {
if (this.retFn) return () => this.retFn!(props, ctx); if (this.retFn) return () => this.retFn!(props, ctx);
else { else {
return () => { return () => {
console.log(ctx.slots.default);
const vNodes = MComponent.vNode( const vNodes = MComponent.vNode(
this.content, this.content,
mountNum mountNum
@ -312,12 +294,19 @@ export class MComponent {
* @param children VNode的内容列表 * @param children VNode的内容列表
* @param mount id * @param mount id
*/ */
static vNode(children: (MotaComponent | VForRenderer)[], mount?: number) { static vNode(
children: (MotaComponent | VForRenderer | VNode)[],
mount?: number
) {
const mountNum = mount ?? this.mountNum++; const mountNum = mount ?? this.mountNum++;
const res: VNode[] = []; const res: VNode[] = [];
const vifRes: Map<number, boolean> = new Map(); const vifRes: Map<number, boolean> = new Map();
children.forEach((v, i) => { children.forEach((v, i) => {
if (isVNode(v)) {
res.push(v);
return;
}
if (v.type === '@v-for') { if (v.type === '@v-for') {
const node = v as VForRenderer; const node = v as VForRenderer;
const items = const items =
@ -346,7 +335,7 @@ export class MComponent {
); );
} }
if (v.dComponent) { if (v.dComponent) {
res.push(h(v.dComponent(), props, v.slots)); res.push(hVue(v.dComponent(), props, v.slots));
} else { } else {
if (v.component instanceof MComponent) { if (v.component instanceof MComponent) {
res.push( res.push(
@ -356,12 +345,12 @@ export class MComponent {
) )
); );
} else { } else {
res.push(h(v.component!, props, v.slots)); res.push(hVue(v.component!, props, v.slots));
} }
} }
} else if (v.type === 'text') { } else if (v.type === 'text') {
res.push( res.push(
h( hVue(
'span', 'span',
typeof v.innerText === 'function' typeof v.innerText === 'function'
? v.innerText() ? v.innerText()
@ -372,15 +361,17 @@ export class MComponent {
const cls = `--mota-component-canvas-${mountNum}`; const cls = `--mota-component-canvas-${mountNum}`;
const mix = !!props.class ? cls + ' ' + props.class : cls; const mix = !!props.class ? cls + ' ' + props.class : cls;
props.class = mix; props.class = mix;
res.push(h('canvas', props, node.slots)); res.push(hVue('canvas', props, node.slots));
} else { } else {
// 这个时候不可能会有插槽,只会有子内容,因此直接渲染子内容 // 这个时候不可能会有插槽,只会有子内容,因此直接渲染子内容
const content = [node.children].flat(2); const content = node.children;
const vn = this.vNode( const vn = this.vNode(
content.map(v => v.content).flat(), content
.map(v => (v instanceof MComponent ? v.content : v))
.flat(),
mountNum mountNum
); );
res.push(h(v.type, props, vn)); res.push(hVue(v.type, props, vn));
} }
} }
}); });
@ -406,7 +397,7 @@ export class MComponent {
* @param props props * @param props props
*/ */
static prop(component: Component, props: Record<string, any>) { static prop(component: Component, props: Record<string, any>) {
return h(component, props); return hVue(component, props);
} }
} }
@ -418,18 +409,101 @@ export function m() {
return new MComponent(); return new MComponent();
} }
/** export namespace MCGenerator {
* VNode export function h(
* @param id id type: string | Component | MComponent,
* @param width children?: MComponentChildren,
* @param height config: MotaComponentConfig = {}
* @param noBoarder ): MotaComponent {
*/ if (typeof type === 'string') {
export function icon( return {
id: AllIds, type,
width?: number, children: ensureArray(children ?? []),
height?: number, props: config.props,
noBoarder?: number innerText: config.innerText,
) { slots: config.slots,
return h(BoxAnimate, { id, width, height, noBoarder }); vif: config.vif,
velse: config.velse,
component: config.component
};
} else {
return {
type: 'component',
children: ensureArray(children ?? []),
props: config.props,
innerText: config.innerText,
slots: config.slots,
vif: config.vif,
velse: config.velse,
component: type
};
}
}
export function div(
children?: MComponentChildren,
config?: NonComponentConfig
): MotaComponent {
return h('div', children, config);
}
export function span(
children?: MComponentChildren,
config?: NonComponentConfig
): MotaComponent {
return h('span', children, config);
}
export function canvas(config?: NonComponentConfig): MotaComponent {
return h('canvas', [], config);
}
export function text(
text: string | (() => string),
config: NonComponentConfig = {}
): MotaComponent {
return h('text', [], { ...config, innerText: text });
}
export function com(
component: Component | MComponent,
config: Omit<MotaComponentConfig, 'innerText' | 'component'>
): MotaComponent {
return h(component, [], config);
}
/**
* VNode
* @param id id
* @param width
* @param height
* @param noBoarder
*/
export function icon(
id: AllIds,
width?: number,
height?: number,
noBoarder?: number
): VNode {
return hVue(BoxAnimate, { id, width, height, noBoarder });
}
export function vfor<T>(
items: T[] | (() => T[]),
map: (value: T, index: number) => VNode
): VForRenderer {
return {
type: '@v-for',
items,
map
};
}
/**
*
* @param value
*/
export function f<T>(value: T): () => T {
return () => value;
}
} }

View File

@ -7,6 +7,8 @@ import { bgm } from '../audio/bgm';
import { SoundEffect } from '../audio/sound'; import { SoundEffect } from '../audio/sound';
import settingsText from '@/data/settings.json'; import settingsText from '@/data/settings.json';
import { isMobile } from '@/plugin/use'; import { isMobile } from '@/plugin/use';
import { fontSize } from '@/plugin/ui/statusBar';
import { CustomToolbar } from './custom/toolbar';
export interface SettingComponentProps { export interface SettingComponentProps {
item: MotaSettingItem; item: MotaSettingItem;
@ -334,6 +336,8 @@ mainSetting.on('valueChange', (key, n, o) => {
handleActionSetting(setting, n, o); handleActionSetting(setting, n, o);
} else if (root === 'audio') { } else if (root === 'audio') {
handleAudioSetting(setting, n, o); handleAudioSetting(setting, n, o);
} else if (root === 'ui') {
handleUiSetting(setting, n, o);
} }
}); });
@ -363,6 +367,11 @@ function handleScreenSetting<T extends number | boolean>(
} else if (key === 'fontSize') { } else if (key === 'fontSize') {
// 字体大小 // 字体大小
root.style.fontSize = `${n}px`; root.style.fontSize = `${n}px`;
const absoluteSize = (n as number) * devicePixelRatio;
storage.setValue('@@absoluteFontSize', absoluteSize);
storage.write();
} else if (key === 'fontSizeStatus') {
fontSize.value = n as number;
} }
} }
@ -394,6 +403,16 @@ function handleAudioSetting<T extends number | boolean>(
} }
} }
function handleUiSetting<T extends number | boolean>(key: string, n: T, o: T) {
if (key === 'toolbarScale') {
const scale = (n as number) / (o as number);
CustomToolbar.list.forEach(v => {
v.setSize(v.width * scale, v.height * scale);
});
CustomToolbar.refreshAll(true);
}
}
// ----- 游戏的所有设置项 // ----- 游戏的所有设置项
// todo: 虚拟键盘缩放,小地图楼传缩放 // todo: 虚拟键盘缩放,小地图楼传缩放
mainSetting mainSetting
@ -407,11 +426,13 @@ mainSetting
.register('heroDetail', '勇士显伤', false, COM.Boolean) .register('heroDetail', '勇士显伤', false, COM.Boolean)
.register('transition', '界面动画', false, COM.Boolean) .register('transition', '界面动画', false, COM.Boolean)
.register('antiAlias', '抗锯齿', false, COM.Boolean) .register('antiAlias', '抗锯齿', false, COM.Boolean)
.register('fontSize', '字体大小', 16, COM.Number, [8, 28, 1]) .register('fontSize', '字体大小', 16, COM.Number, [2, 48, 1])
.register('fontSizeStatus', '状态栏字体', 16, COM.Number, [2, 48, 1])
.register('smoothView', '平滑镜头', true, COM.Boolean) .register('smoothView', '平滑镜头', true, COM.Boolean)
.register('criticalGem', '临界显示方式', false, COM.Boolean) .register('criticalGem', '临界显示方式', false, COM.Boolean)
.setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击')) .setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击'))
.register('keyScale', '虚拟键盘缩放', 100, COM.Number, [25, 5, 500]) .register('keyScale', '虚拟键盘缩放', 100, COM.Number, [25, 5, 500])
.register('blur', '背景虚化', !isMobile, COM.Boolean)
) )
.register( .register(
'action', 'action',
@ -450,13 +471,13 @@ mainSetting
.register( .register(
'ui', 'ui',
'ui设置', 'ui设置',
new MotaSetting().register( new MotaSetting()
'mapScale', .register('mapScale', '小地图缩放', 100, COM.Number, [50, 1000, 50])
'小地图楼传缩放', .setDisplayFunc('mapScale', value => `${value}%`)
100, .register('toolbarScale', '工具栏缩放', 100, COM.Number, [10, 500, 10])
COM.Number, .setDisplayFunc('toolbarScale', value => `${value}%`)
[50, 1000, 50] .register('bookScale', '怪物手册缩放', 100, COM.Number, [10, 500, 10])
) .setDisplayFunc('bookScale', value => `${value}%`)
); );
const loading = Mota.require('var', 'loading'); const loading = Mota.require('var', 'loading');
@ -471,6 +492,7 @@ loading.once('coreInit', () => {
'screen.fontSize': storage.getValue('screen.fontSize', 16), 'screen.fontSize': storage.getValue('screen.fontSize', 16),
'screen.smoothView': !!storage.getValue('screen.smoothView', true), 'screen.smoothView': !!storage.getValue('screen.smoothView', true),
'screen.criticalGem': !!storage.getValue('screen.criticalGem', false), 'screen.criticalGem': !!storage.getValue('screen.criticalGem', false),
'screen.fontSizeStatus': storage.getValue('screen.fontSizeStatus', 100),
'action.fixed': !!storage.getValue('action.fixed', true), 'action.fixed': !!storage.getValue('action.fixed', true),
'audio.bgmEnabled': !!storage.getValue('audio.bgmEnabled', true), 'audio.bgmEnabled': !!storage.getValue('audio.bgmEnabled', true),
'audio.bgmVolume': storage.getValue('audio.bgmVolume', 80), 'audio.bgmVolume': storage.getValue('audio.bgmVolume', 80),
@ -483,7 +505,12 @@ loading.once('coreInit', () => {
'ui.mapScale': storage.getValue( 'ui.mapScale': storage.getValue(
'ui.mapScale', 'ui.mapScale',
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50 isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
) ),
'ui.toolbarScale': storage.getValue(
'ui.toolbarScale',
isMobile ? 50 : Math.floor((window.innerWidth / 1700) * 10) * 10
),
'ui.bookScale': storage.getValue('ui.bookScale', isMobile ? 100 : 80),
}); });
}); });
@ -515,4 +542,22 @@ mainSetting
.setDescription('audio.bgmVolume', `背景音乐的音量`) .setDescription('audio.bgmVolume', `背景音乐的音量`)
.setDescription('audio.soundEnabled', `是否开启音效`) .setDescription('audio.soundEnabled', `是否开启音效`)
.setDescription('audio.soundVolume', `音效的音量`) .setDescription('audio.soundVolume', `音效的音量`)
.setDescription('ui.mapScale', `楼传小地图的缩放,百分比格式`); .setDescription('ui.mapScale', `楼传小地图的缩放,百分比格式`)
.setDescription('ui.toolbarScale', `自定义工具栏的缩放比例`)
.setDescription('ui.bookScale', `怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度`)
.setDescription('screen.fontSizeStatus', `修改状态栏的字体大小`)
.setDescription('screen.blur', '打开任意ui界面时是否有背景虚化效果移动端打开后可能会有掉帧或者发热现象。关闭ui后生效');
function setFontSize() {
const absoluteSize = storage.getValue(
'@@absoluteFontSize',
16 * devicePixelRatio
);
const size = Math.round(absoluteSize / devicePixelRatio);
mainSetting.setValue('screen.fontSize', size);
}
setFontSize();
window.addEventListener('resize', () => {
setFontSize();
});

View File

@ -4,6 +4,7 @@ import { loading } from '../game';
export interface CurrentEnemy { export interface CurrentEnemy {
enemy: DamageEnemy; enemy: DamageEnemy;
// 这个是干啥的?
onMapEnemy: DamageEnemy[]; onMapEnemy: DamageEnemy[];
} }
@ -16,155 +17,64 @@ export function getEnemy(
return v.x === x && v.y === y; return v.x === x && v.y === y;
}); });
if (!enemy) { if (!enemy) {
throw new Error( return null;
`Get null when getting enemy on '${x},${y}' in '${floorId}'`
);
} }
return enemy; return enemy;
} }
function init() { function init() {
core.enemys.canBattle = function canBattle( core.enemys.canBattle = function canBattle(
x: number, x: number | DamageEnemy,
y: number, y: number,
floorId: FloorIds = core.status.floorId floorId: FloorIds = core.status.floorId
) { ) {
const enemy = getEnemy(x, y, floorId); const enemy = typeof x === 'number' ? getEnemy(x, y, floorId) : x;
if (!enemy) {
throw new Error(
`Cannot get enemy on x:${x}, y:${y}, floor: ${floorId}`
);
}
const { damage } = enemy.calDamage(); const { damage } = enemy.calDamage();
return damage < core.status.hero.hp; return damage < core.status.hero.hp;
}; };
core.events.battle = function battle( core.events.battle = function battle(
x: number, x: number | DamageEnemy,
y: number, y: number,
force: boolean = false, force: boolean = false,
callback?: () => void callback?: () => void
) { ) {
core.saveAndStopAutomaticRoute(); core.saveAndStopAutomaticRoute();
const enemy = getEnemy(x, y); const isLoc = typeof x === 'number';
const enemy = isLoc ? getEnemy(x, y) : x;
if (!enemy) {
throw new Error(
`Cannot battle with enemy since no enemy on ${x},${y}`
);
}
// 非强制战斗 // 非强制战斗
// @ts-ignore
if (!core.canBattle(x, y) && !force && !core.status.event.id) { if (!core.canBattle(x, y) && !force && !core.status.event.id) {
core.stopSound(); core.stopSound();
core.playSound('操作失败'); core.playSound('操作失败');
core.drawTip('你打不过此怪物!', enemy.id); core.drawTip('你打不过此怪物!', enemy!.id);
return core.clearContinueAutomaticRoute(callback); return core.clearContinueAutomaticRoute(callback);
} }
// 自动存档 // 自动存档
if (!core.status.event.id) core.autosave(true); if (!core.status.event.id) core.autosave(true);
// 战前事件 // 战前事件
// 战后事件 // 战后事件
core.afterBattle(enemy, x, y); core.afterBattle(enemy, isLoc ? x : enemy.x, y);
callback?.(); callback?.();
}; };
core.events.afterBattle = function afterBattle( const getFacedId = (enemy: DamageEnemy) => {
enemy: DamageEnemy, const e = enemy.enemy;
x?: number,
y?: number
) {
const floorId = core.status.floorId;
const special = enemy.info.special;
// 播放战斗动画 if (e.displayIdInBook) return e.displayIdInBook;
let animate: AnimationIds = 'hand'; if (e.faceIds) return e.faceIds.down;
// 检查当前装备是否存在攻击动画 return e.id;
const equipId = core.getEquip(0);
if (equipId && (core.material.items[equipId].equip || {}).animate)
animate = core.material.items[equipId].equip.animate;
// 检查该动画是否存在SE如果不存在则使用默认音效
if (!core.material.animates[animate]?.se) core.playSound('attack.mp3');
// 战斗伤害
const info = enemy.calDamage(core.status.hero);
const damage = info.damage;
// 判定是否致死
if (damage >= core.status.hero.hp) {
core.status.hero.hp = 0;
core.updateStatusBar(false, true);
core.events.lose('战斗失败');
return;
}
// 扣减体力值并记录统计数据
core.status.hero.hp -= damage;
core.status.hero.statistics.battleDamage += damage;
core.status.hero.statistics.battle++;
// 智慧之源
if (special.includes(14) && flags.hard === 2) {
core.addFlag(
'inte_' + floorId,
Math.ceil((core.status.hero.mdef / 10) * 0.3) * 10
);
core.status.hero.mdef -=
Math.ceil((core.status.hero.mdef / 10) * 0.3) * 10;
}
// 极昼永夜
if (special.includes(22)) {
flags[`night_${floorId}`] ??= 0;
flags[`night_${floorId}`] -= enemy.enemy.night!;
}
if (special.includes(23)) {
flags[`night_${floorId}`] ??= 0;
flags[`night_${floorId}`] += enemy.enemy.day;
}
// if (core.plugin.skillTree.getSkillLevel(11) > 0) {
// core.plugin.study.declineStudiedSkill();
// }
// 如果是融化怪,需要特殊标记一下
if (special.includes(25) && has(x) && has(y)) {
flags[`melt_${floorId}`] ??= {};
flags[`melt_${floorId}`][`${x},${y}`] = enemy.enemy.melt;
}
// 获得金币
const money = enemy.enemy.money;
core.status.hero.money += money;
core.status.hero.statistics.money += money;
// 获得经验
const exp = enemy.enemy.exp;
core.status.hero.exp += exp;
core.status.hero.statistics.exp += exp;
const hint =
'打败 ' + enemy.enemy.name + ',金币+' + money + ',经验+' + exp;
core.drawTip(hint, enemy.id);
if (core.getFlag('bladeOn') && core.getFlag('blade')) {
core.setFlag('blade', false);
}
if (core.getFlag('shieldOn') && core.getFlag('shield')) {
core.setFlag('shield', false);
}
// 事件的处理
const todo: MotaEvent = [];
// 战后事件
if (has(core.status.floorId)) {
const loc = `${x},${y}` as LocString;
todo.push(
...(core.floors[core.status.floorId].afterBattle[loc] ?? [])
);
}
todo.push(...(enemy.enemy.afterBattle ?? []));
// 如果事件不为空,将其插入
if (todo.length > 0) core.insertAction(todo, x, y);
if (has(x) && has(y)) {
core.drawAnimate(animate, x, y);
core.removeBlock(x, y);
} else core.drawHeroAnimate(animate);
// 如果已有事件正在处理中
if (core.status.event.id == null) core.continueAutomaticRoute();
else core.clearContinueAutomaticRoute();
}; };
core.enemys.getCurrentEnemys = function getCurrentEnemys( core.enemys.getCurrentEnemys = function getCurrentEnemys(
@ -176,7 +86,8 @@ function init() {
ensureFloorDamage(floorId); ensureFloorDamage(floorId);
const floor = core.status.maps[floorId]; const floor = core.status.maps[floorId];
floor.enemy.list.forEach(v => { floor.enemy.list.forEach(v => {
if (!(v.id in used)) { const id = getFacedId(v);
if (!(id in used)) {
const e = new DamageEnemy(v.enemy); const e = new DamageEnemy(v.enemy);
e.calAttribute(); e.calAttribute();
e.getRealInfo(); e.getRealInfo();
@ -186,9 +97,9 @@ function init() {
onMapEnemy: [v] onMapEnemy: [v]
}; };
enemys.push(curr); enemys.push(curr);
used[v.id] = curr.onMapEnemy; used[id] = curr.onMapEnemy;
} else { } else {
used[v.id].push(v); used[id].push(v);
} }
}); });
@ -207,7 +118,7 @@ function init() {
const enemy = getEnemy(data.x, data.y); const enemy = getEnemy(data.x, data.y);
beforeBattle.push(...(floor.beforeBattle[loc] ?? [])); beforeBattle.push(...(floor.beforeBattle[loc] ?? []));
beforeBattle.push(...(enemy.enemy.beforeBattle ?? [])); beforeBattle.push(...(enemy!.enemy.beforeBattle ?? []));
if (beforeBattle.length > 0) { if (beforeBattle.length > 0) {
beforeBattle.push({ type: 'battle', x: data.x, y: data.y }); beforeBattle.push({ type: 'battle', x: data.x, y: data.y });
@ -251,5 +162,22 @@ loading.once('coreInit', init);
declare global { declare global {
interface Enemys { interface Enemys {
getCurrentEnemys(floorId: FloorIds): CurrentEnemy[]; getCurrentEnemys(floorId: FloorIds): CurrentEnemy[];
canBattle(enemy: DamageEnemy, _?: number, floorId?: FloorIds): boolean;
canBattle(x: number, y: number, floorId?: FloorIds): boolean;
}
interface Events {
battle(
enemy: DamageEnemy,
_?: number,
force?: boolean,
callback?: () => void
): void;
battle(
x: number,
y?: number,
force?: boolean,
callback?: () => void
): void;
} }
} }

View File

@ -16,15 +16,15 @@ interface HaloType {
}; };
} }
interface EnemyInfo { interface EnemyInfo extends Partial<Enemy> {
atk: number; atk: number;
def: number; def: number;
hp: number; hp: number;
special: number[]; special: number[];
damageDecline: number; damageDecline: number;
atkBuff: number; atkBuff_: number;
defBuff: number; defBuff_: number;
hpBuff: number; hpBuff_: number;
enemy: Enemy; enemy: Enemy;
x?: number; x?: number;
y?: number; y?: number;
@ -65,7 +65,7 @@ interface CriticalDamageDelta extends Omit<DamageDelta, 'info'> {
type HaloFn = (info: EnemyInfo, enemy: Enemy) => void; type HaloFn = (info: EnemyInfo, enemy: Enemy) => void;
/** 光环属性 */ /** 光环属性 */
export const haloSpecials: number[] = [8, 21, 25, 26, 27]; export const haloSpecials: Set<number> = new Set([8, 21, 25, 26, 27]);
export class EnemyCollection implements RangeCollection<DamageEnemy> { export class EnemyCollection implements RangeCollection<DamageEnemy> {
floorId: FloorIds; floorId: FloorIds;
@ -302,7 +302,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
info!: EnemyInfo; info!: EnemyInfo;
/** 向其他怪提供过的光环 */ /** 向其他怪提供过的光环 */
providedHalo: number[] = []; providedHalo: Set<number> = new Set();
/** /**
* 0 -> -> 1 -> -> 2 -> provide inject * 0 -> -> 1 -> -> 2 -> provide inject
@ -334,16 +334,23 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
def: enemy.def, def: enemy.def,
special: enemy.special.slice(), special: enemy.special.slice(),
damageDecline: 0, damageDecline: 0,
atkBuff: 0, atkBuff_: 0,
defBuff: 0, defBuff_: 0,
hpBuff: 0, hpBuff_: 0,
enemy: this.enemy, enemy: this.enemy,
x: this.x, x: this.x,
y: this.y, y: this.y,
floorId: this.floorId floorId: this.floorId
}; };
for (const [key, value] of Object.entries(enemy)) {
if (!(key in this.info) && has(value)) {
// @ts-ignore
this.info[key] = value;
}
}
this.progress = 0; this.progress = 0;
this.providedHalo = []; this.providedHalo.clear();
} }
/** /**
@ -370,8 +377,8 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
for (const [loc, per] of Object.entries(flags[`melt_${floorId}`])) { for (const [loc, per] of Object.entries(flags[`melt_${floorId}`])) {
const [mx, my] = loc.split(',').map(v => parseInt(v)); const [mx, my] = loc.split(',').map(v => parseInt(v));
if (Math.abs(mx - this.x) <= 1 && Math.abs(my - this.y) <= 1) { if (Math.abs(mx - this.x) <= 1 && Math.abs(my - this.y) <= 1) {
info.atkBuff += per as number; info.atkBuff_ += per as number;
info.defBuff += per as number; info.defBuff_ += per as number;
} }
} }
} }
@ -392,9 +399,9 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
// 此时已经inject光环因此直接计算真实属性 // 此时已经inject光环因此直接计算真实属性
const info = this.info; const info = this.info;
info.atk = Math.floor(info.atk * (info.atkBuff / 100 + 1)); info.atk = Math.floor(info.atk * (info.atkBuff_ / 100 + 1));
info.def = Math.floor(info.def * (info.defBuff / 100 + 1)); info.def = Math.floor(info.def * (info.defBuff_ / 100 + 1));
info.hp = Math.floor(info.hp * (info.hpBuff / 100 + 1)); info.hp = Math.floor(info.hp * (info.hpBuff_ / 100 + 1));
return this.info; return this.info;
} }
@ -404,7 +411,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
if (!has(this.x) || !has(this.y)) return []; if (!has(this.x) || !has(this.y)) return [];
const special = this.info.special ?? this.enemy.special; const special = this.info.special ?? this.enemy.special;
const filter = special.filter(v => { const filter = special.filter(v => {
return haloSpecials.includes(v) && !this.providedHalo.includes(v); return haloSpecials.has(v) && !this.providedHalo.has(v);
}); });
if (filter.length === 0) return []; if (filter.length === 0) return [];
const collection = this.col ?? core.status.maps[this.floorId].enemy; const collection = this.col ?? core.status.maps[this.floorId].enemy;
@ -448,11 +455,11 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
e.special.includes(8) && e.special.includes(8) &&
(e.x !== this.x || this.y !== e.y) (e.x !== this.x || this.y !== e.y)
) { ) {
e.atkBuff += enemy.together ?? 0; e.atkBuff_ += enemy.together ?? 0;
e.defBuff += enemy.together ?? 0; e.defBuff_ += enemy.together ?? 0;
} }
}); });
this.providedHalo.push(8); this.providedHalo.add(8);
} }
// 冰封光环 // 冰封光环
@ -460,7 +467,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
square7.push(e => { square7.push(e => {
e.damageDecline += this.enemy.iceHalo ?? 0; e.damageDecline += this.enemy.iceHalo ?? 0;
}); });
this.providedHalo.push(21); this.providedHalo.add(21);
col.haloList.push({ col.haloList.push({
type: 'square', type: 'square',
data: { x: this.x, y: this.y, d: 7 }, data: { x: this.x, y: this.y, d: 7 },
@ -472,9 +479,9 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
// 冰封之核 // 冰封之核
if (special.includes(26)) { if (special.includes(26)) {
square5.push(e => { square5.push(e => {
e.defBuff += this.enemy.iceCore ?? 0; e.defBuff_ += this.enemy.iceCore ?? 0;
}); });
this.providedHalo.push(26); this.providedHalo.add(26);
col.haloList.push({ col.haloList.push({
type: 'square', type: 'square',
data: { x: this.x, y: this.y, d: 5 }, data: { x: this.x, y: this.y, d: 5 },
@ -486,9 +493,9 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
// 火焰之核 // 火焰之核
if (special.includes(27)) { if (special.includes(27)) {
square5.push(e => { square5.push(e => {
e.atkBuff += this.enemy.fireCore ?? 0; e.atkBuff_ += this.enemy.fireCore ?? 0;
}); });
this.providedHalo.push(27); this.providedHalo.add(27);
col.haloList.push({ col.haloList.push({
type: 'square', type: 'square',
data: { x: this.x, y: this.y, d: 5 }, data: { x: this.x, y: this.y, d: 5 },
@ -617,7 +624,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
) { ) {
damage[loc] ??= { damage: 0, type: new Set() }; damage[loc] ??= { damage: 0, type: new Set() };
damage[loc].damage += dam; damage[loc].damage += dam;
damage[loc].type.add(type); if (type) damage[loc].type.add(type);
} }
private calEnemyDamageOf(hero: Partial<HeroStatus>, enemy: EnemyInfo) { private calEnemyDamageOf(hero: Partial<HeroStatus>, enemy: EnemyInfo) {
@ -830,6 +837,9 @@ const skills: [unlock: string, condition: string][] = [
['shieldOn', 'shield'] ['shieldOn', 'shield']
]; ];
const haloValue: Map<number, SelectKey<Enemy, number | undefined>[]> =
new Map();
/** /**
* *
* @param info * @param info
@ -946,13 +956,6 @@ export function getSingleEnemy(id: EnemyIds) {
} }
declare global { declare global {
interface PluginDeclaration {
damage: {
Enemy: typeof DamageEnemy;
Collection: typeof EnemyCollection;
};
}
interface Floor { interface Floor {
enemy: EnemyCollection; enemy: EnemyCollection;
} }

View File

@ -84,14 +84,16 @@ export interface GameEvent extends EmitableEvent {
mounted: () => void; mounted: () => void;
/** Emitted in plugin/ui.js */ /** Emitted in plugin/ui.js */
statusBarUpdate: () => void; statusBarUpdate: () => void;
/** Emitted in libs/events.js */ /** Emitted in core/index.ts */
afterGetItem: ( renderLoaded: () => void;
itemId: AllIdsOf<'items'>, // /** Emitted in libs/events.js */
x: number, // afterGetItem: (
y: number, // itemId: AllIdsOf<'items'>,
isGentleClick: boolean // x: number,
) => void; // y: number,
afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void; // isGentleClick: boolean
// ) => void;
// afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
} }
export const hook = new EventEmitter<GameEvent>(); export const hook = new EventEmitter<GameEvent>();
@ -112,7 +114,7 @@ class GameListener extends EventEmitter<ListenerEvent> {
constructor() { constructor() {
super(); super();
if (main.replayChecking) return;
if (!!window.core) { if (!!window.core) {
this.init(); this.init();
} else { } else {
@ -131,14 +133,19 @@ class GameListener extends EventEmitter<ListenerEvent> {
const getBlockLoc = (px: number, py: number, size: number) => { const getBlockLoc = (px: number, py: number, size: number) => {
return [ return [
Math.floor(((px * 32) / size - core.bigmap.offsetX) / 32), Math.floor(((px * 32) / size + core.bigmap.offsetX) / 32),
Math.floor(((py * 32) / size - core.bigmap.offsetY) / 32) Math.floor(((py * 32) / size + core.bigmap.offsetY) / 32)
]; ];
}; };
// hover & leave & mouseMove // hover & leave & mouseMove
data.addEventListener('mousemove', e => { data.addEventListener('mousemove', e => {
if (core.status.lockControl || !core.isPlaying()) return; if (
core.status.lockControl ||
!core.isPlaying() ||
!core.status.floorId
)
return;
this.emit('mouseMove', e); this.emit('mouseMove', e);
const { const {
x: px, x: px,
@ -164,7 +171,12 @@ class GameListener extends EventEmitter<ListenerEvent> {
} }
}); });
data.addEventListener('mouseleave', e => { data.addEventListener('mouseleave', e => {
if (core.status.lockControl || !core.isPlaying()) return; if (
core.status.lockControl ||
!core.isPlaying() ||
!core.status.floorId
)
return;
const blocks = core.getMapBlocksObj(); const blocks = core.getMapBlocksObj();
const lastBlock = blocks[`${lastHoverX},${lastHoverY}`]; const lastBlock = blocks[`${lastHoverX},${lastHoverY}`];
if (!!lastBlock) { if (!!lastBlock) {
@ -175,7 +187,12 @@ class GameListener extends EventEmitter<ListenerEvent> {
}); });
// click // click
data.addEventListener('click', e => { data.addEventListener('click', e => {
if (core.status.lockControl || !core.isPlaying()) return; if (
core.status.lockControl ||
!core.isPlaying() ||
!core.status.floorId
)
return;
const { const {
x: px, x: px,
y: py, y: py,

View File

@ -18,11 +18,7 @@ import type { Keyboard } from '@/core/main/custom/keyboard';
import type { CustomToolbar } from '@/core/main/custom/toolbar'; import type { CustomToolbar } from '@/core/main/custom/toolbar';
import type { Focus, GameUi, UiController } from '@/core/main/custom/ui'; import type { Focus, GameUi, UiController } from '@/core/main/custom/ui';
import type { gameListener, hook } from './game'; import type { gameListener, hook } from './game';
import type { import type { MotaSetting, SettingDisplayer } from '@/core/main/setting';
MotaSetting,
SettingDisplayer,
SettingStorage
} from '@/core/main/setting';
import type { GameStorage } from '@/core/main/storage'; import type { GameStorage } from '@/core/main/storage';
import type { DamageEnemy, EnemyCollection } from './enemy/damage'; import type { DamageEnemy, EnemyCollection } from './enemy/damage';
import type { specials } from './enemy/special'; import type { specials } from './enemy/special';
@ -87,7 +83,7 @@ interface VariableInterface {
sound: SoundController; sound: SoundController;
resource: ResourceStore<Exclude<ResourceType, 'zip'>>; resource: ResourceStore<Exclude<ResourceType, 'zip'>>;
zipResource: ResourceStore<'zip'>; zipResource: ResourceStore<'zip'>;
settingStorage: GameStorage<SettingStorage>; settingStorage: GameStorage;
status: Ref<boolean>; status: Ref<boolean>;
// 定义于游戏进程,渲染进程依然可用 // 定义于游戏进程,渲染进程依然可用
haloSpecials: number[]; haloSpecials: number[];

View File

@ -8,7 +8,7 @@
* Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx * Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
* But these are "more general", as they should work across browsers & OS`s. * But these are "more general", as they should work across browsers & OS`s.
*/ */
export enum KeyCode { export enum KeyCode {
DependsOnKbLayout = -1, DependsOnKbLayout = -1,
/** /**

View File

@ -1,3 +1,4 @@
import { ref } from 'vue'; import { ref } from 'vue';
export const status = ref(false); export const status = ref(false);
export const fontSize = ref(100);

View File

@ -71,6 +71,11 @@ type Enemy<I extends EnemyIds = EnemyIds> = {
*/ */
displayIdInBook: EnemyIds; displayIdInBook: EnemyIds;
/**
*
*/
faceIds: Record<Dir, EnemyIds>;
/** /**
* *
*/ */

26
src/types/event.d.ts vendored
View File

@ -99,22 +99,6 @@ interface Events extends EventData {
*/ */
trigger(x: number, y: number, callback?: () => void): void; trigger(x: number, y: number, callback?: () => void): void;
/**
*
* @example core.battle('greenSlime'); // 和从天而降的绿头怪战斗(如果打得过)
* @param id id
* @param x
* @param y
* @param force true表示强制战斗
* @param callback
*/
battle(
x: number,
y: number,
force: boolean = false,
callback?: () => void
): void;
/** /**
* *
* @example core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒 * @example core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒
@ -428,6 +412,7 @@ interface Events extends EventData {
): void; ): void;
/** /**
* @deprecated
* *
* @example core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0 * @example core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0
* @param id id * @param id id
@ -447,6 +432,7 @@ interface Events extends EventData {
): void; ): void;
/** /**
* @deprecated
* *
* @param x * @param x
* @param y * @param y
@ -469,6 +455,7 @@ interface Events extends EventData {
): void; ): void;
/** /**
* @deprecated
* *
* @param x * @param x
* @param y * @param y
@ -483,6 +470,7 @@ interface Events extends EventData {
): void; ): void;
/** /**
* @deprecated
* *
* @param fromX * @param fromX
* @param fromY * @param fromY
@ -756,7 +744,11 @@ interface Events extends EventData {
* @example core.tryUseItem('pickaxe'); // 尝试使用破墙镐 * @example core.tryUseItem('pickaxe'); // 尝试使用破墙镐
* @param itemId id * @param itemId id
*/ */
tryUseItem(itemId: ItemIdOf<'tools' | 'constants'>): void; tryUseItem(
itemId: ItemIdOf<'tools' | 'constants'>,
noRoute?: boolean,
callback?: () => void
): void;
_sys_battle(data: Block, callback?: () => void): void; _sys_battle(data: Block, callback?: () => void): void;

View File

@ -50,6 +50,8 @@ import { getDetailedEnemy } from '../plugin/ui/fixed';
import { GameUi } from '@/core/main/custom/ui'; import { GameUi } from '@/core/main/custom/ui';
import { gameKey } from '@/core/main/init/hotkey'; import { gameKey } from '@/core/main/init/hotkey';
import { mainUi } from '@/core/main/init/ui'; import { mainUi } from '@/core/main/init/ui';
import { mainSetting } from '@/core/main/setting';
import { isMobile } from '@/plugin/use';
const props = defineProps<{ const props = defineProps<{
num: number; num: number;
@ -70,6 +72,14 @@ const drag = ref(false);
const detail = ref(false); const detail = ref(false);
const selected = ref(0); const selected = ref(0);
const settingScale = mainSetting.getValue('ui.bookScale', 100) / 100;
const scale = isMobile
? Math.max(settingScale * 15, 20)
: Math.max(
(window.innerWidth / window.innerHeight) * 15 * settingScale,
20
);
/** /**
* 选择怪物展示详细信息 * 选择怪物展示详细信息
* @param enemy 选择的怪物 * @param enemy 选择的怪物
@ -121,11 +131,13 @@ async function exit() {
const hold = mainUi.holdOn(); const hold = mainUi.holdOn();
mainUi.close(props.num); mainUi.close(props.num);
if (core.events.recoverEvents(core.status.event.interval)) { if (core.events.recoverEvents(core.status.event.interval)) {
hold.end(true);
return; return;
} else if (has(core.status.event.ui)) { } else if (has(core.status.event.ui)) {
core.status.boxAnimateObjs = []; core.status.boxAnimateObjs = [];
// @ts-ignore // @ts-ignore
core.ui._drawViewMaps(core.status.event.ui); core.ui._drawViewMaps(core.status.event.ui);
hold.end(true);
} else hold.end(); } else hold.end();
} }
@ -141,42 +153,44 @@ function checkScroll() {
} }
// //
gameKey.use(props.ui.symbol); setTimeout(() => {
gameKey gameKey.use(props.ui.symbol);
.realize('@book_up', () => { gameKey
if (selected.value > 0) { .realize('@book_up', () => {
selected.value--; if (selected.value > 0) {
} selected.value--;
checkScroll(); }
}) checkScroll();
.realize('@book_down', () => { })
if (selected.value < enemy.length - 1) { .realize('@book_down', () => {
selected.value++; if (selected.value < enemy.length - 1) {
} selected.value++;
checkScroll(); }
}) checkScroll();
.realize('@book_pageDown', () => { })
if (selected.value <= 4) { .realize('@book_pageDown', () => {
selected.value = 0; if (selected.value <= 4) {
} else { selected.value = 0;
selected.value -= 5; } else {
} selected.value -= 5;
checkScroll(); }
}) checkScroll();
.realize('@book_pageUp', () => { })
if (selected.value >= enemy.length - 5) { .realize('@book_pageUp', () => {
selected.value = enemy.length - 1; if (selected.value >= enemy.length - 5) {
} else { selected.value = enemy.length - 1;
selected.value += 5; } else {
} selected.value += 5;
checkScroll(); }
}) checkScroll();
.realize('exit', () => { })
exit(); .realize('exit', () => {
}) exit();
.realize('confirm', () => { })
select(toShow[selected.value], selected.value); .realize('confirm', () => {
}); select(toShow[selected.value], selected.value);
});
}, 0);
onUnmounted(() => { onUnmounted(() => {
gameKey.dispose(props.ui.symbol); gameKey.dispose(props.ui.symbol);
@ -188,7 +202,6 @@ onUnmounted(() => {
user-select: none; user-select: none;
width: 80%; width: 80%;
height: 100%; height: 100%;
font-family: 'normal';
overflow: hidden; overflow: hidden;
transition: opacity 0.6s linear; transition: opacity 0.6s linear;
display: flex; display: flex;
@ -208,25 +221,28 @@ onUnmounted(() => {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-family: 'normal';
} }
.enemy { .enemy {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 20vh; height: v-bind('scale + "vh"');
width: 100%; width: 100%;
padding: 0 1% 0 1%; padding: 0 1% 0 1%;
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
#tools {
transform: translateY(-50%);
}
#book { #book {
width: 100%; width: 100%;
padding: 5%; padding: 5%;
} }
.enemy { .enemy {
height: 15vh; height: v-bind('scale * 2 / 3 + "vh"');
} }
} }
</style> </style>

View File

@ -1,6 +1,11 @@
<!-- 怪物详细信息 --> <!-- 怪物详细信息 -->
<template> <template>
<div id="detail"> <div id="detail">
<div id="tools">
<span id="back" class="button-text tools" @click="close"
><left-outlined />返回</span
>
</div>
<div id="info" :style="{ top: `${top}px` }"> <div id="info" :style="{ top: `${top}px` }">
<EnemyOne :enemy="enemy!"></EnemyOne> <EnemyOne :enemy="enemy!"></EnemyOne>
<a-divider <a-divider
@ -172,7 +177,7 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 72%; width: 72%;
height: 90%; height: 100%;
transition: all 0.6s ease; transition: all 0.6s ease;
user-select: none; user-select: none;
} }
@ -208,6 +213,15 @@ onUnmounted(() => {
opacity: 0; opacity: 0;
} }
#tools {
position: fixed;
height: 6%;
font-size: 3.2vh;
width: 100%;
left: 5%;
top: 5%;
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
#detail { #detail {
width: 100%; width: 100%;
@ -220,7 +234,11 @@ onUnmounted(() => {
font-size: 4vw; font-size: 4vw;
bottom: 5%; bottom: 5%;
left: 5vw; left: 5vw;
width: 90vw; width: 80vw;
}
#info {
transform: translateY(10%);
} }
} }
</style> </style>

View File

@ -701,14 +701,9 @@ onUnmounted(() => {
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
#equipbox {
padding: 5%;
}
#equipbox-main { #equipbox-main {
height: 90vh;
flex-direction: column-reverse; flex-direction: column-reverse;
font-size: 100%; font-size: 225%;
} }
#equip-now-div { #equip-now-div {
@ -721,7 +716,11 @@ onUnmounted(() => {
} }
#equip-list { #equip-list {
flex-basis: 50%; flex-basis: 45%;
#filter #sort-type {
font-size: 150%;
}
} }
.divider { .divider {

View File

@ -56,15 +56,15 @@
:type="isMobile ? 'horizontal' : 'vertical'" :type="isMobile ? 'horizontal' : 'vertical'"
></a-divider> ></a-divider>
<div id="fly-right"> <div id="fly-right">
<canvas id="fly-thumbnail" @click="fly"></canvas> <canvas id="fly-thumbnail" @click="fly" @wheel="wheel"></canvas>
<div id="fly-tools"> <div id="fly-tools">
<double-left-outlined <double-left-outlined
@click="changeFloorByDelta(-10)" @click="changeFloorByDelta(-10)"
class="button-text" class="button-text fly-button"
/> />
<left-outlined <left-outlined
@click="changeFloorByDelta(-1)" @click="changeFloorByDelta(-1)"
class="button-text" class="button-text fly-button"
/> />
<span <span
class="changable" class="changable"
@ -74,11 +74,11 @@
> >
<right-outlined <right-outlined
@click="changeFloorByDelta(1)" @click="changeFloorByDelta(1)"
class="button-text" class="button-text fly-button"
/> />
<double-right-outlined <double-right-outlined
@click="changeFloorByDelta(10)" @click="changeFloorByDelta(10)"
class="button-text" class="button-text fly-button"
/> />
</div> </div>
</div> </div>
@ -472,6 +472,10 @@ function click(e: MouseEvent) {
} }
} }
function wheel(ev: WheelEvent) {
changeFloorByDelta(-Math.sign(ev.deltaY));
}
function changeAreaByFloor(id: FloorIds) { function changeAreaByFloor(id: FloorIds) {
nowArea.value = Object.keys(area).find(v => area[v].includes(id))!; nowArea.value = Object.keys(area).find(v => area[v].includes(id))!;
} }
@ -725,6 +729,7 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
position: relative;
} }
#fly-tools { #fly-tools {
@ -734,11 +739,17 @@ onUnmounted(() => {
flex-direction: row; flex-direction: row;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
position: absolute;
bottom: 0;
width: 100%;
background-color: #0004;
} }
#fly-thumbnail { #fly-thumbnail {
width: 35vw; width: 35vw;
height: 35vw; height: 35vw;
max-height: 75vh;
max-width: 75vh;
border: 0.1vw solid #ddd4; border: 0.1vw solid #ddd4;
} }
@ -766,10 +777,27 @@ onUnmounted(() => {
background-color: #ddd4; background-color: #ddd4;
} }
.fly-button {
padding: 3%;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.301);
border: 1px dashed #ddda;
filter: drop-shadow(0px 0px 16px black);
}
#fly-now {
text-wrap: nowrap;
white-space: nowrap;
max-width: 50%;
text-overflow: ellipsis;
overflow: hidden;
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
-1px -1px 1px black;
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
#fly { #fly {
padding: 5%; font-size: 225%;
font-size: 100%;
} }
#fly-main { #fly-main {

View File

@ -242,7 +242,7 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
width: 50%; width: 50%;
text-overflow: clip; text-overflow: clip;
align-items: end; align-items: flex-end;
text-align: end; text-align: end;
.hotkey-one-set-item { .hotkey-one-set-item {

View File

@ -108,7 +108,7 @@ function update() {
info.damage = enemy.enemy.calDamage().damage; info.damage = enemy.enemy.calDamage().damage;
const critical = enemy.enemy.calCritical()[0]; const critical = enemy.enemy.calCritical()[0];
info.critical = critical?.atkDelta ?? 0; info.critical = critical?.atkDelta ?? 0;
info.criticalDam = critical.delta ?? 0; info.criticalDam = critical?.delta ?? 0;
info.defDamage = enemy.enemy.calDefDamage(ratio).delta; info.defDamage = enemy.enemy.calDefDamage(ratio).delta;
} }

View File

@ -46,15 +46,19 @@
</TransitionGroup> </TransitionGroup>
</div> </div>
<div class="setting-info"> <div class="setting-info">
<div <Scroll class="info-text-scroll">
class="info-text" <div
v-html="splitText(display.at(-1)?.text ?? ['请选择设置'])" class="info-text"
></div> v-html="
splitText(display.at(-1)?.text ?? ['请选择设置'])
"
></div>
</Scroll>
<a-divider class="info-divider" dashed></a-divider> <a-divider class="info-divider" dashed></a-divider>
<div class="info-editor" v-if="!!selectedItem"> <div class="info-editor" v-if="!!selectedItem">
<div class="editor-custom"> <div class="editor-custom">
<component <component
:is="selectedItem.controller" :is="(selectedItem.controller as any)"
:item="selectedItem" :item="selectedItem"
:displayer="displayer" :displayer="displayer"
:setting="setting" :setting="setting"
@ -68,16 +72,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { computed, onUnmounted, ref, shallowRef } from 'vue';
import { import {
mainSetting, mainSetting,
MotaSetting, MotaSetting,
MotaSettingItem, MotaSettingItem,
SettingDisplayer, SettingDisplayer,
SettingDisplayInfo, SettingDisplayInfo
SettingText
} from '../core/main/setting'; } from '../core/main/setting';
import settingText from '../data/settings.json';
import { RightOutlined, LeftOutlined } from '@ant-design/icons-vue'; import { RightOutlined, LeftOutlined } from '@ant-design/icons-vue';
import { splitText } from '../plugin/utils'; import { splitText } from '../plugin/utils';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
@ -88,13 +90,11 @@ import { mainUi } from '@/core/main/init/ui';
const props = defineProps<{ const props = defineProps<{
info?: MotaSetting; info?: MotaSetting;
text?: SettingText;
num: number; num: number;
ui: GameUi; ui: GameUi;
}>(); }>();
const setting = props.info ?? mainSetting; const setting = props.info ?? mainSetting;
const text = props.text ?? (settingText as SettingText);
const display = shallowRef<SettingDisplayInfo[]>([]); const display = shallowRef<SettingDisplayInfo[]>([]);
const selectedItem = computed(() => display.value.at(-1)?.item); const selectedItem = computed(() => display.value.at(-1)?.item);
const update = ref(false); const update = ref(false);
@ -312,6 +312,11 @@ onUnmounted(() => {
.info-text { .info-text {
font-size: 85%; font-size: 85%;
min-height: 30%; min-height: 30%;
max-height: 50%;
}
.info-text-scroll {
max-height: 50%;
} }
} }
@ -321,7 +326,7 @@ onUnmounted(() => {
} }
.setting-main { .setting-main {
font-size: 120%; font-size: 225%;
.setting-container { .setting-container {
flex-direction: column; flex-direction: column;

View File

@ -460,7 +460,7 @@ onUnmounted(() => {
#shop { #shop {
width: 90vw; width: 90vw;
padding-top: 5vh; padding-top: 5vh;
font-size: 100%; font-size: 225%;
} }
#item-list { #item-list {

View File

@ -130,13 +130,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, shallowReactive, watch } from 'vue'; import { onMounted, onUnmounted, ref, shallowReactive, watch } from 'vue';
import Box from '../components/box.vue'; import Box from '../components/box.vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { status } from '../plugin/ui/statusBar'; import { status } from '../plugin/ui/statusBar';
import { isMobile } from '../plugin/use'; import { isMobile } from '../plugin/use';
import { has } from '../plugin/utils'; import { fontSize } from '../plugin/ui/statusBar';
import { has } from '@/plugin/utils';
let main: HTMLDivElement;
const items = core.flags.statusBarItems;
const icons = core.statusBar.icons;
const skillTree = Mota.Plugin.require('skillTree_g'); const skillTree = Mota.Plugin.require('skillTree_g');
const width = ref( const width = ref(
@ -148,6 +153,7 @@ const format = core.formatBigNumber;
watch(width, n => (updateStatus.value = !updateStatus.value)); watch(width, n => (updateStatus.value = !updateStatus.value));
watch(height, n => (updateStatus.value = !updateStatus.value)); watch(height, n => (updateStatus.value = !updateStatus.value));
watch(fontSize, n => (main.style.fontSize = `${isMobile ? n * 1.5 : n}%`));
const hero = shallowReactive<Partial<HeroStatus>>({}); const hero = shallowReactive<Partial<HeroStatus>>({});
const keys = shallowReactive<number[]>([]); const keys = shallowReactive<number[]>([]);
@ -230,8 +236,24 @@ function viewMap() {
function openStudy() {} function openStudy() {}
function resize() {
requestAnimationFrame(() => {
main.style.fontSize = `${
isMobile ? fontSize.value * 1.5 : fontSize.value
}%`;
});
}
onMounted(() => { onMounted(() => {
update(); update();
main = document.getElementById('status-main') as HTMLDivElement;
window.addEventListener('resize', resize);
resize();
});
onUnmounted(() => {
window.removeEventListener('resize', resize);
}); });
</script> </script>
@ -241,14 +263,16 @@ onMounted(() => {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 1vh 0; padding: 1vh 0;
font-size: v-bind(fontSize);
} }
.status-item { .status-item {
position: relative; display: flex;
flex-direction: row;
max-width: 17.5vw; max-width: 17.5vw;
font-size: 200%; font-size: 200%;
width: 100%; width: 100%;
margin-bottom: 1vh; margin-bottom: 14px;
text-shadow: 3px 2px 3px #000, 0px 0px 3px #111; text-shadow: 3px 2px 3px #000, 0px 0px 3px #111;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -266,6 +290,10 @@ onMounted(() => {
margin-left: 10%; margin-left: 10%;
} }
.status-value {
transform: translateY(2px);
}
#status-header { #status-header {
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -3,41 +3,64 @@
<span class="button-text" @click="exit"><left-outlined /> 返回</span> <span class="button-text" @click="exit"><left-outlined /> 返回</span>
</div> </div>
<div id="tool-editor"> <div id="tool-editor">
<Scroll class="tool-list-scroll"> <div id="tool-left">
<div id="tool-list"> <Scroll class="tool-list-scroll">
<div <div id="tool-list">
v-for="(item, i) of list"
class="tool-list-item selectable"
:selected="i === selected"
@click="selected = i"
>
<span>{{ item.id }}</span>
<a-button
type="danger"
class="tool-list-delete"
@click.stop="deleteTool(item.id)"
>删除</a-button
>
</div>
<div id="tool-list-add">
<div <div
id="tool-add-div" v-for="(item, i) of list"
@click="addingTool = true" class="tool-list-item selectable"
v-if="!addingTool" :selected="i === selected"
@click="selected = i"
> >
<PlusOutlined></PlusOutlined>&nbsp;&nbsp; <span>{{ item.id }}</span>
<span>新增工具栏</span> <a-button
type="danger"
class="tool-list-delete"
@click.stop="deleteTool(item.id)"
>删除</a-button
>
</div> </div>
<div v-else> <div id="tool-list-add">
<a-input <div
style="height: 100%; font-size: 80%; width: 100%" id="tool-add-div"
v-model:value="addingToolId" @click="addingTool = true"
@blur="addTool" v-if="!addingTool"
></a-input> >
<PlusOutlined></PlusOutlined>&nbsp;&nbsp;
<span>新增工具栏</span>
</div>
<div v-else>
<a-input
style="
height: 100%;
font-size: 80%;
width: 100%;
"
v-model:value="addingToolId"
@blur="addTool"
></a-input>
</div>
</div>
</div>
</Scroll>
<a-divider
type="vertical"
dashed
v-if="isMobile"
style="height: 100%; border-color: #ddd4"
></a-divider>
<div id="tool-preview" v-if="!!bar && isMobile">
<div id="tool-preview-container">
<div class="tool-preview-item" v-for="item of bar.items">
<component
:is="(CustomToolbar.info[item.type].show as any)"
:item="item"
:toolbar="bar"
></component>
</div> </div>
</div> </div>
</div> </div>
</Scroll> </div>
<a-divider <a-divider
class="divider" class="divider"
dashed dashed
@ -151,8 +174,8 @@
</div> </div>
</Scroll> </Scroll>
</div> </div>
<a-divider dashed></a-divider> <a-divider dashed v-if="!isMobile"></a-divider>
<div id="tool-preview" v-if="!!bar"> <div id="tool-preview" v-if="!!bar && !isMobile">
<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
@ -181,12 +204,15 @@ import { isMobile } from '@/plugin/use';
import Scroll from '@/components/scroll.vue'; import Scroll from '@/components/scroll.vue';
import { deleteWith, tip } from '@/plugin/utils'; import { deleteWith, tip } from '@/plugin/utils';
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { mainSetting } from '@/core/main/setting';
const props = defineProps<{ const props = defineProps<{
ui: GameUi; ui: GameUi;
num: number; num: number;
}>(); }>();
const scale = mainSetting.getValue('ui.toolbarScale', 100) / 100;
const list = CustomToolbar.list; const list = CustomToolbar.list;
const selected = ref(0); const selected = ref(0);
@ -331,7 +357,13 @@ onUnmounted(() => {
.tool-list-scroll { .tool-list-scroll {
height: 100%; height: 100%;
width: 30%; width: 100%;
}
#tool-left {
flex-basis: 30%;
display: flex;
height: 100%;
} }
#tool-list { #tool-list {
@ -478,7 +510,7 @@ onUnmounted(() => {
height: 40%; height: 40%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: start; align-items: flex-start;
#tool-preview-container { #tool-preview-container {
width: 90%; width: 90%;
@ -491,9 +523,9 @@ onUnmounted(() => {
.tool-preview-item { .tool-preview-item {
display: flex; display: flex;
margin: 5px; margin: v-bind('5 * scale + "px"');
min-width: 50px; min-width: v-bind('50 * scale + "px"');
height: 50px; height: v-bind('50 * scale + "px"');
background-color: #222; background-color: #222;
border: 1.5px solid #ddd8; border: 1.5px solid #ddd8;
justify-content: center; justify-content: center;
@ -509,4 +541,45 @@ onUnmounted(() => {
.divider { .divider {
height: 100%; height: 100%;
} }
@media screen and (max-width: 600px) {
#tool-editor {
padding-top: 15%;
flex-direction: column;
width: 100%;
height: 100%;
}
#tool-left {
width: 100%;
flex-basis: 40%;
max-height: 40vh;
display: flex;
flex-direction: row;
.tool-list-scroll {
height: 100%;
flex-basis: 50%;
}
#tool-preview {
flex-basis: 50%;
height: 100%;
}
}
#tool-info {
width: 100%;
flex-basis: 60%;
#tool-detail {
height: 100%;
}
}
.divider {
height: auto;
width: 100%;
}
}
</style> </style>

View File

@ -28,6 +28,7 @@
import Box from '@/components/box.vue'; import Box from '@/components/box.vue';
import { CustomToolbar } from '@/core/main/custom/toolbar'; import { CustomToolbar } from '@/core/main/custom/toolbar';
import { GameUi } from '@/core/main/custom/ui'; import { GameUi } from '@/core/main/custom/ui';
import { mainSetting } from '@/core/main/setting';
import { onUnmounted, reactive, watch } from 'vue'; import { onUnmounted, reactive, watch } from 'vue';
interface BoxData { interface BoxData {
@ -44,6 +45,7 @@ const props = defineProps<{
}>(); }>();
const bar = props.bar; const bar = props.bar;
const scale = mainSetting.getValue('ui.toolbarScale', 100) / 100;
const box = reactive<BoxData>({ const box = reactive<BoxData>({
x: bar.x, x: bar.x,
@ -80,7 +82,7 @@ onUnmounted(() => {
<style lang="less" scoped> <style lang="less" scoped>
.toolbar-container { .toolbar-container {
background-color: #0009; background-color: #0009;
padding: 5px; padding: v-bind('scale * 5 + "px"');
} }
.toolbar { .toolbar {
@ -93,9 +95,9 @@ onUnmounted(() => {
.toolbar-item { .toolbar-item {
display: flex; display: flex;
margin: 5px; margin: v-bind('scale * 5 + "px"');
min-width: 50px; min-width: v-bind('scale * 50 + "px"');
height: 50px; height: v-bind('scale * 50 + "px"');
cursor: pointer; cursor: pointer;
background-color: #222; background-color: #222;
border: 1.5px solid #ddd8; border: 1.5px solid #ddd8;
@ -106,7 +108,7 @@ onUnmounted(() => {
.toolbar-item::v-deep(> *) { .toolbar-item::v-deep(> *) {
height: 100%; height: 100%;
min-width: 50px; min-width: v-bind('scale * 50 + "px"');
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -182,9 +182,9 @@ function use(id: ShowItemIds) {
const hold = mainUi.holdOn(); const hold = mainUi.holdOn();
exit(); exit();
nextTick(() => { nextTick(() => {
core.useItem(id, false, () => { core.tryUseItem(id, false, () => {
if (mainUi.stack.length === 0) { if (mainUi.stack.length === 0) {
hold.end(); hold.end(core.status.event.id !== 'toolbox');
} }
}); });
}); });
@ -377,31 +377,23 @@ onUnmounted(() => {
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
flex-basis: 50%;
#desc-text { #desc-text {
margin-top: 2vh; margin-top: 2vh;
margin-left: 0.5vw; margin-left: 0.5vw;
width: 100%; width: 100%;
height: 100%; max-height: 100%;
} }
} }
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
#toolbox {
padding: 5%;
}
#tools {
span {
margin: 0;
}
}
#toolbox-main { #toolbox-main {
flex-direction: column-reverse; flex-direction: column-reverse;
height: 100%; height: 90%;
font-size: 100%; font-size: 225%;
margin-top: 10%;
} }
.item-list { .item-list {
@ -418,5 +410,16 @@ onUnmounted(() => {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
} }
#detail {
flex-basis: 30%;
#desc {
#desc-text {
max-height: 10vh;
height: 10vh;
}
}
}
} }
</style> </style>