diff --git a/src/core/main/init/hotkey.ts b/src/core/main/init/hotkey.ts index 6314c6d..7d6d808 100644 --- a/src/core/main/init/hotkey.ts +++ b/src/core/main/init/hotkey.ts @@ -506,18 +506,14 @@ gameKey .realize('special', () => { if (hovered) { const { x, y } = hovered; - const enemy = core.status.thisMap.enemy.list.find(v => { - return v.x === x && v.y === y; - }); + const enemy = core.status.thisMap.enemy.get(x, y); if (enemy) mainUi.open('fixedDetail', { panel: 'special' }); } }) .realize('critical', () => { if (hovered) { const { x, y } = hovered; - const enemy = core.status.thisMap.enemy.list.find(v => { - return v.x === x && v.y === y; - }); + const enemy = core.status.thisMap.enemy.get(x, y); if (enemy) mainUi.open('fixedDetail', { panel: 'critical' }); } }) diff --git a/src/game/enemy/battle.ts b/src/game/enemy/battle.ts index ccc51a1..35133d8 100644 --- a/src/game/enemy/battle.ts +++ b/src/game/enemy/battle.ts @@ -13,12 +13,7 @@ export function getEnemy( y: number, floorId: FloorIds = core.status.floorId ) { - const enemy = core.status.maps[floorId].enemy.list.find(v => { - return v.x === x && v.y === y; - }); - if (!enemy) { - return null; - } + const enemy = core.status.maps[floorId].enemy.get(x, y); return enemy; } diff --git a/src/game/enemy/damage.ts b/src/game/enemy/damage.ts index 8adafce..063fda8 100644 --- a/src/game/enemy/damage.ts +++ b/src/game/enemy/damage.ts @@ -1,5 +1,5 @@ import { getHeroStatusOf, getHeroStatusOn } from '@/game/state/hero'; -import { Range, RangeCollection } from '@/plugin/game/range'; +import { Range } from '../util/range'; import { ensureArray, has, manhattan } from '@/plugin/game/utils'; import EventEmitter from 'eventemitter3'; @@ -72,6 +72,7 @@ type HaloFn = (info: EnemyInfo, enemy: EnemyInfo) => void; export const haloSpecials: Set = new Set([ 8, 21, 25, 26, 27, 29, 31, 32 ]); +/** 不可被同化的属性 */ export const unassimilatable: Set = new Set(haloSpecials); unassimilatable.add(8).add(30); /** 特殊属性对应 */ @@ -101,18 +102,20 @@ interface EnemyCollectionEvent { calculated: []; } -export class EnemyCollection - extends EventEmitter - implements RangeCollection -{ +export class EnemyCollection extends EventEmitter { floorId: FloorIds; - list: DamageEnemy[] = []; + list: Map = new Map(); - range: Range = new Range(this); - // todo: 改成Map + range: Range = new Range(); + /** 地图伤害 */ mapDamage: Record = {}; haloList: HaloData[] = []; + /** 楼层宽度 */ + width: number = 0; + /** 楼层高度 */ + height: number = 0; + /** 乾坤挪移属性 */ translation: [number, number] = [0, 0]; @@ -123,20 +126,27 @@ export class EnemyCollection } get(x: number, y: number) { - return this.list.find(v => v.x === x && v.y === y); + const index = x + y * this.width; + return this.list.get(index); } /** * 解析本地图的怪物信息 */ extract() { - this.list = []; + this.list.clear(); core.extractBlocks(this.floorId); - core.status.maps[this.floorId].blocks.forEach(v => { + const floor = core.status.maps[this.floorId]; + this.width = floor.width; + this.height = floor.height; + floor.blocks.forEach(v => { if (v.disable) return; if (v.event.cls !== 'enemy48' && v.event.cls !== 'enemys') return; + const { x, y } = v; + const index = x + y * this.width; const enemy = core.material.enemys[v.event.id as EnemyIds]; - this.list.push( + this.list.set( + index, new DamageEnemy(enemy, v.x, v.y, this.floorId, this) ); }); @@ -201,7 +211,7 @@ export class EnemyCollection recursion: boolean = false ) { const arr = ensureArray(halo); - const enemys = this.range.scan(type, data); + const enemys = this.range.type(type).scan(this.list.values(), data); if (!recursion) { arr.forEach(v => { enemys.forEach(e => { diff --git a/src/game/index.ts b/src/game/index.ts index 80105fb..699d604 100644 --- a/src/game/index.ts +++ b/src/game/index.ts @@ -2,13 +2,11 @@ import './system'; import '../plugin/game/index'; import * as damage from './enemy/damage'; import { EventEmitter, IndexedEventEmitter } from '@/core/common/eventEmitter'; -import { Range } from '@/plugin/game/range'; import { specials } from './enemy/special'; import { gameListener, hook, loading } from './game'; import * as battle from './enemy/battle'; import * as hero from './state/hero'; import * as miscMechanism from './mechanism/misc'; -import * as study from './mechanism/study'; import { registerPresetState } from './state/preset'; import { ItemState } from './state/item'; import { @@ -23,7 +21,6 @@ Mota.register('class', 'DamageEnemy', damage.DamageEnemy); Mota.register('class', 'EnemyCollection', damage.EnemyCollection); Mota.register('class', 'EventEmitter', EventEmitter); Mota.register('class', 'IndexedEventEmitter', IndexedEventEmitter); -Mota.register('class', 'Range', Range); // ----- 函数注册 Mota.register('fn', 'getEnemy', battle.getEnemy); Mota.register('fn', 'getHeroStatusOn', hero.getHeroStatusOn); @@ -36,8 +33,7 @@ Mota.register('var', 'gameListener', gameListener); Mota.register('var', 'loading', loading); // ----- 模块注册 Mota.register('module', 'Mechanism', { - BluePalace: miscMechanism.BluePalace, - Study: study + BluePalace: miscMechanism.BluePalace }); Mota.register('module', 'State', { ItemState, diff --git a/src/game/mechanism/study.ts b/src/game/mechanism/study.ts deleted file mode 100644 index c5d1470..0000000 --- a/src/game/mechanism/study.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { has } from '@/plugin/game/utils'; -import { EnemyInfo } from '../enemy/damage'; -import { getSkillLevel } from '@/plugin/game/skillTree'; -import { hook } from '../game'; - -// 负责勇士技能:学习 -const canStudy: Set = new Set([1, 4, 5, 6, 7, 10, 11, 20, 28, 30]); -const numberProp: Record = { - 1: ['crit'], - 6: ['n'], - 7: ['hungry'], - 10: ['courage'], - 11: ['charge'], - 20: ['ice'], - 28: ['paleShield'] -}; - -hook.on('afterBattle', () => { - declineStudiedSkill(); -}); - -/** - * 判断是否可以学习某个技能 - * @param number 要学习的技能 - */ -export function canStudySkill(number: number) { - const s = (core.status.hero.special ??= { num: [], last: [] }); - if (Mota.Plugin.require('skillTree_g').getSkillLevel(11) === 0) - return false; - if (s.num.length >= 1) return false; - if (s.num.includes(number)) return false; - return canStudy.has(number); -} - -/** - * 学习某个怪物的某个技能 - * @param enemy 被学习的怪物 - * @param number 学习的技能 - */ -export function studySkill(enemy: EnemyInfo, number: number) { - core.status.hero.special ??= { num: [], last: [] }; - const s = core.status.hero.special; - if (!canStudySkill(number)) return false; - s.num.push(number); - s.last.push(getSkillLevel(11) * 3 + 2); - const value = numberProp[number] ?? []; - for (const key of value) { - s[key] = enemy[key]; - } - return true; -} - -/** - * 忘记某个学习过的技能 - * @param num 要忘记的技能 - * @param i 技能所在索引 - */ -export function forgetStudiedSkill(num: number, i: number) { - const s = core.status.hero.special; - const index = has(i) ? i : s.num.indexOf(num); - if (index === -1) return; - s.num.splice(index, 1); - s.last.splice(index, 1); - const value = numberProp[num] ?? []; - for (const key of value) { - delete s[key]; - } -} - -/** - * 战斗后减少剩余次数 - */ -export function declineStudiedSkill() { - const s = (core.status.hero.special ??= { num: [], last: [] }); - for (let i = 0; i < s.last.length; i++) { - s.last[i]--; - if (s.last[i] === 0) { - forgetStudiedSkill(s.num[i], i); - } - } -} diff --git a/src/game/system.ts b/src/game/system.ts index a980085..0e2f837 100644 --- a/src/game/system.ts +++ b/src/game/system.ts @@ -16,7 +16,6 @@ import type { MotaSetting, SettingDisplayer } from '@/core/main/setting'; import type { GameStorage } from '@/core/main/storage'; import type { DamageEnemy, EnemyCollection } from './enemy/damage'; import type { specials } from './enemy/special'; -import type { Range } from '@/plugin/game/range'; import type { KeyCode } from '@/plugin/keyCodes'; import type { Ref } from 'vue'; import type * as battle from './enemy/battle'; @@ -63,7 +62,6 @@ interface ClassInterface { Danmaku: typeof Danmaku; // todo: 放到插件 ShaderEffect: typeof ShaderEffect; // 定义于游戏进程,渲染进程依然可用 - Range: typeof Range; EnemyCollection: typeof EnemyCollection; DamageEnemy: typeof DamageEnemy; } diff --git a/src/game/util/range.ts b/src/game/util/range.ts new file mode 100644 index 0000000..f6938df --- /dev/null +++ b/src/game/util/range.ts @@ -0,0 +1,81 @@ +import { isNil } from 'lodash-es'; + +interface RangeTypeData { + square: { x: number; y: number; d: number }; + rect: { x: number; y: number; w: number; h: number }; +} + +type InRangeFn, T> = (item: E, data: T) => boolean; + +export class Range { + static rangeType: Record = {}; + + /** + * 获取一个范围类型,并进行判断 + * @param type 范围类型 + */ + type( + type: T + ): T extends keyof RangeTypeData ? RangeType : RangeType { + return Range.rangeType[type] as T extends keyof RangeTypeData + ? RangeType + : RangeType; + } + + /** + * 注册一个新的范围类型 + * @param type 范围类型 + * @param fn 判断是否在范围内的函数 + */ + static register( + type: K, + fn: InRangeFn, RangeTypeData[K]> + ): void; + static register(type: string, fn: InRangeFn, any>): void; + static register(type: string, fn: InRangeFn, any>): void { + const range = new RangeType(type, fn); + this.rangeType[type] = range; + } +} + +class RangeType { + readonly type: string; + /** + * 判断一个元素是否在指定范围内 + * @param item 要判断的元素 + * @param data 范围数据 + */ + readonly inRange: InRangeFn, Type>; + + constructor(type: string, fn: InRangeFn, Type>) { + this.type = type; + this.inRange = fn; + } + + /** + * 扫描一个列表中所有在范围内的元素 + * @param items 元素列表 + * @param data 范围数据 + */ + scan>(items: Iterable, data: Type): T[] { + const res: T[] = []; + for (const ele of items) { + if (this.inRange(ele, data)) { + res.push(ele); + } + } + return res; + } +} + +Range.register('square', (item, { x, y, d }) => { + if (isNil(item.x) || isNil(item.y)) return false; + const r = Math.floor(d / 2); + return Math.abs(item.x - x) <= r && Math.abs(item.y - y) <= r; +}); +Range.register('rect', (item, { x, y, w, h }) => { + if (isNil(item.x) || isNil(item.y)) return false; + const ex = x + w; + const ey = y + h; + return item.x >= x && item.y >= y && item.x < ex && item.y < ey; +}); diff --git a/src/plugin/game/range.ts b/src/plugin/game/range.ts deleted file mode 100644 index 77e4991..0000000 --- a/src/plugin/game/range.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { has } from './utils'; - -type RangeScanFn> = ( - collection: Range, - data: any -) => C[]; -type InRangeFn> = ( - collection: Range, - data: any, - item: Partial -) => boolean; - -interface RangeType> { - scan: RangeScanFn; - inRange: InRangeFn; -} - -export interface RangeCollection> { - list: I[]; - range: Range; -} - -export class Range> { - collection: RangeCollection; - cache: Record = {}; - - static rangeType: Record>> = {}; - - constructor(collection: RangeCollection) { - this.collection = collection; - } - - /** - * 扫描 collection 中在范围内的物品 - * @param type 范围类型 - * @param data 范围数据 - * @returns 在范围内的物品列表 - */ - scan(type: string, data: any): C[] { - const t = Range.rangeType[type]; - if (!t) { - throw new Error(`Unknown range type: ${type}.`); - } - return t.scan(this, data) as C[]; - } - - inRange(type: string, data: any, item: Partial) { - const t = Range.rangeType[type]; - if (!t) { - throw new Error(`Unknown range type: ${type}.`); - } - return t.inRange(this, data, item); - } - - clearCache() { - this.cache = {}; - } - - static registerRangeType( - type: string, - scan: RangeScanFn>, - inRange: InRangeFn> - ) { - Range.rangeType[type] = { - scan, - inRange - }; - } -} - -// ----- 默认的范围类型 - -// 方形区域 -Range.registerRangeType( - 'square', - (col, { x, y, d }) => { - const list = col.collection.list; - const r = Math.floor(d / 2); - - return list.filter(v => { - return ( - has(v.x) && - has(v.y) && - Math.abs(v.x - x) <= r && - Math.abs(v.y - y) <= r - ); - }); - }, - (col, { x, y, d }, item) => { - const r = Math.floor(d / 2); - return ( - has(item.x) && - has(item.y) && - Math.abs(item.x - x) <= r && - Math.abs(item.y - y) <= r - ); - } -); -Range.registerRangeType( - 'rect', - (col, { x, y, w, h }) => { - const list = col.collection.list; - const ex = x + w; - const ey = y + h; - - return list.filter(v => { - return ( - has(v.x) && - has(v.y) && - v.x >= x && - v.y >= y && - v.x < ex && - v.y < ey - ); - }); - }, - (col, { x, y, w, h }, item) => { - const ex = x + w; - const ey = y + h; - const v = item; - - return ( - has(v.x) && has(v.y) && v.x >= x && v.y >= y && v.x < ex && v.y < ey - ); - } -); diff --git a/src/plugin/game/replay.ts b/src/plugin/game/replay.ts index b86bce9..f848cf4 100644 --- a/src/plugin/game/replay.ts +++ b/src/plugin/game/replay.ts @@ -1,6 +1,4 @@ -import { canStudySkill, studySkill } from '@/game/mechanism/study'; import { upgradeSkill } from './skillTree'; -import { ensureFloorDamage } from '@/game/enemy/damage'; const replayableSettings = ['autoSkill']; diff --git a/src/plugin/ui/fly.ts b/src/plugin/ui/fly.ts index 68c5e9b..d888dd4 100644 --- a/src/plugin/ui/fly.ts +++ b/src/plugin/ui/fly.ts @@ -524,7 +524,7 @@ export class MinimapDrawer { ctx.fillRect(x - 6, y - 2, 12, 4); ctx.fillStyle = 'white'; const enemy = core.status.maps[floorId].enemy.list; - if (enemy.length === 0) { + if (enemy.size === 0) { ctx.strokeStyle = 'lightgreen'; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; @@ -533,8 +533,12 @@ export class MinimapDrawer { ctx.lineTo(x - 0.5, y + 1); ctx.lineTo(x + 1.5, y - 1); ctx.stroke(); - } else if (enemy.length <= 2) { - const ids = [...new Set(enemy.map(v => v.id))]; + } else if (enemy.size <= 2) { + const idSet = new Set(); + enemy.forEach(v => { + idSet.add(v.id); + }); + const ids: EnemyIds[] = [...idSet]; if (ids.length === 1) { core.drawIcon(ctx, ids[0], x - 2, y - 2, 4, 4); } else if (ids.length === 2) { @@ -552,7 +556,7 @@ export class MinimapDrawer { ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(`+${enemy.length}`, x, y); + ctx.fillText(`+${enemy.size}`, x, y); } ctx.restore(); diff --git a/src/ui/fixedDetail.vue b/src/ui/fixedDetail.vue index 33db5c2..e0eab24 100644 --- a/src/ui/fixedDetail.vue +++ b/src/ui/fixedDetail.vue @@ -28,9 +28,7 @@ detailInfo.pos = 0; if (hovered) { const { x, y } = hovered; - const enemy = core.status.thisMap.enemy.list.find(v => { - return v.x === x && v.y === y; - }); + const enemy = core.status.thisMap.enemy.get(x, y); if (enemy) { const detail = getDetailedEnemy(enemy); detailInfo.enemy = detail; diff --git a/src/ui/index.ts b/src/ui/index.ts index e82afca..e20f162 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -16,7 +16,6 @@ export { default as Skill } from './skill.vue'; export { default as SkillTree } from './skillTree.vue'; export { default as Start } from './start.vue'; export { default as StatusBar } from './statusBar.vue'; -export { default as Study } from './study.vue'; export { default as Toolbox } from './toolbox.vue'; export { default as Hotkey } from './hotkey.vue'; export { default as Toolbar } from './toolbar.vue'; diff --git a/src/ui/study.vue b/src/ui/study.vue deleted file mode 100644 index 3a8c6b5..0000000 --- a/src/ui/study.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - - -