mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-31 15:09:26 +08:00
refactor: Range
This commit is contained in:
parent
39f38cd703
commit
42f611b357
@ -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' });
|
||||
}
|
||||
})
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<number> = new Set([
|
||||
8, 21, 25, 26, 27, 29, 31, 32
|
||||
]);
|
||||
/** 不可被同化的属性 */
|
||||
export const unassimilatable: Set<number> = new Set(haloSpecials);
|
||||
unassimilatable.add(8).add(30);
|
||||
/** 特殊属性对应 */
|
||||
@ -101,18 +102,20 @@ interface EnemyCollectionEvent {
|
||||
calculated: [];
|
||||
}
|
||||
|
||||
export class EnemyCollection
|
||||
extends EventEmitter<EnemyCollectionEvent>
|
||||
implements RangeCollection<DamageEnemy>
|
||||
{
|
||||
export class EnemyCollection extends EventEmitter<EnemyCollectionEvent> {
|
||||
floorId: FloorIds;
|
||||
list: DamageEnemy[] = [];
|
||||
list: Map<number, DamageEnemy> = new Map();
|
||||
|
||||
range: Range<DamageEnemy> = new Range(this);
|
||||
// todo: 改成Map<number, MapDamage>
|
||||
range: Range = new Range();
|
||||
/** 地图伤害 */
|
||||
mapDamage: Record<string, MapDamage> = {};
|
||||
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 => {
|
||||
|
@ -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,
|
||||
|
@ -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<number> = new Set([1, 4, 5, 6, 7, 10, 11, 20, 28, 30]);
|
||||
const numberProp: Record<number, (keyof Enemy)[]> = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
81
src/game/util/range.ts
Normal file
81
src/game/util/range.ts
Normal file
@ -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<E extends Partial<Loc>, T> = (item: E, data: T) => boolean;
|
||||
|
||||
export class Range {
|
||||
static rangeType: Record<string, RangeType> = {};
|
||||
|
||||
/**
|
||||
* 获取一个范围类型,并进行判断
|
||||
* @param type 范围类型
|
||||
*/
|
||||
type<T extends string>(
|
||||
type: T
|
||||
): T extends keyof RangeTypeData ? RangeType<RangeTypeData[T]> : RangeType {
|
||||
return Range.rangeType[type] as T extends keyof RangeTypeData
|
||||
? RangeType<RangeTypeData[T]>
|
||||
: RangeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个新的范围类型
|
||||
* @param type 范围类型
|
||||
* @param fn 判断是否在范围内的函数
|
||||
*/
|
||||
static register<K extends keyof RangeTypeData>(
|
||||
type: K,
|
||||
fn: InRangeFn<Partial<Loc>, RangeTypeData[K]>
|
||||
): void;
|
||||
static register(type: string, fn: InRangeFn<Partial<Loc>, any>): void;
|
||||
static register(type: string, fn: InRangeFn<Partial<Loc>, any>): void {
|
||||
const range = new RangeType(type, fn);
|
||||
this.rangeType[type] = range;
|
||||
}
|
||||
}
|
||||
|
||||
class RangeType<Type = any> {
|
||||
readonly type: string;
|
||||
/**
|
||||
* 判断一个元素是否在指定范围内
|
||||
* @param item 要判断的元素
|
||||
* @param data 范围数据
|
||||
*/
|
||||
readonly inRange: InRangeFn<Partial<Loc>, Type>;
|
||||
|
||||
constructor(type: string, fn: InRangeFn<Partial<Loc>, Type>) {
|
||||
this.type = type;
|
||||
this.inRange = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描一个列表中所有在范围内的元素
|
||||
* @param items 元素列表
|
||||
* @param data 范围数据
|
||||
*/
|
||||
scan<T extends Partial<Loc>>(items: Iterable<T>, 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;
|
||||
});
|
@ -1,126 +0,0 @@
|
||||
import { has } from './utils';
|
||||
|
||||
type RangeScanFn<C extends Partial<Loc>> = (
|
||||
collection: Range<C>,
|
||||
data: any
|
||||
) => C[];
|
||||
type InRangeFn<C extends Partial<Loc>> = (
|
||||
collection: Range<C>,
|
||||
data: any,
|
||||
item: Partial<Loc>
|
||||
) => boolean;
|
||||
|
||||
interface RangeType<C extends Partial<Loc>> {
|
||||
scan: RangeScanFn<C>;
|
||||
inRange: InRangeFn<C>;
|
||||
}
|
||||
|
||||
export interface RangeCollection<I extends Partial<Loc>> {
|
||||
list: I[];
|
||||
range: Range<I>;
|
||||
}
|
||||
|
||||
export class Range<C extends Partial<Loc>> {
|
||||
collection: RangeCollection<C>;
|
||||
cache: Record<string, any> = {};
|
||||
|
||||
static rangeType: Record<string, RangeType<Partial<Loc>>> = {};
|
||||
|
||||
constructor(collection: RangeCollection<C>) {
|
||||
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<Loc>) {
|
||||
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<Partial<Loc>>,
|
||||
inRange: InRangeFn<Partial<Loc>>
|
||||
) {
|
||||
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
|
||||
);
|
||||
}
|
||||
);
|
@ -1,6 +1,4 @@
|
||||
import { canStudySkill, studySkill } from '@/game/mechanism/study';
|
||||
import { upgradeSkill } from './skillTree';
|
||||
import { ensureFloorDamage } from '@/game/enemy/damage';
|
||||
|
||||
const replayableSettings = ['autoSkill'];
|
||||
|
||||
|
@ -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<EnemyIds>();
|
||||
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();
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<div id="study"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#study {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user