mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-31 15:09:26 +08:00
feat: Hero state & Item state
This commit is contained in:
parent
5d24906364
commit
1df2bc66f4
@ -17,9 +17,11 @@
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"@emotion/css": "^11.11.2",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"anon-tokyo": "0.0.0-alpha.0",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.5.0",
|
||||
"chart.js": "^4.4.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@ -14,6 +14,9 @@ dependencies:
|
||||
'@vueuse/core':
|
||||
specifier: ^10.4.1
|
||||
version: 10.4.1(vue@3.3.4)
|
||||
anon-tokyo:
|
||||
specifier: 0.0.0-alpha.0
|
||||
version: 0.0.0-alpha.0
|
||||
ant-design-vue:
|
||||
specifier: ^3.2.20
|
||||
version: 3.2.20(vue@3.3.4)
|
||||
@ -23,6 +26,9 @@ dependencies:
|
||||
chart.js:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
eventemitter3:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
gl-matrix:
|
||||
specifier: ^3.4.3
|
||||
version: 3.4.3
|
||||
@ -2696,6 +2702,12 @@ packages:
|
||||
indent-string: 4.0.0
|
||||
dev: true
|
||||
|
||||
/anon-tokyo@0.0.0-alpha.0:
|
||||
resolution: {integrity: sha512-4kq9NOB56RUC6YqZAkkuA2mLhfzdLa39RSi+dUOk6geL4rldWspBZP2XbKv3hhG8nf+HDL2LSOTb7opSbqY/gg==}
|
||||
dependencies:
|
||||
lodash-es: 4.17.21
|
||||
dev: false
|
||||
|
||||
/ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -22,7 +22,7 @@ type EmitFn<F extends (...params: any) => any> = (
|
||||
|
||||
type Key = number | string | symbol;
|
||||
|
||||
export class EventEmitter<T extends Record<keyof T, Callable>> {
|
||||
export class EventEmitter<T extends Record<keyof T, Callable> = {}> {
|
||||
protected events: {
|
||||
[x: Key]: Listener<Callable>[];
|
||||
} = {};
|
||||
|
@ -1200,7 +1200,7 @@ export class Damage extends Sprite {
|
||||
/** 描边样式 */
|
||||
strokeColor: CanvasStyle = '#000';
|
||||
/** 描边粗细 */
|
||||
strokeWidth: number = 1.5;
|
||||
strokeWidth: number = 2;
|
||||
|
||||
constructor(floor?: FloorIds) {
|
||||
super();
|
||||
|
113
src/game/hero.ts
113
src/game/hero.ts
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus;
|
||||
export function getHeroStatusOn(
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOn<K extends keyof HeroStatus>(
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOn(
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
// @ts-ignore
|
||||
return getHeroStatusOf(core.status.hero, name, floorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一定状态下的勇士在某一点的属性
|
||||
* @param status 勇士的状态
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: 'all',
|
||||
floorId?: FloorIds
|
||||
): HeroStatus;
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOf<K extends keyof HeroStatus>(
|
||||
status: Partial<HeroStatus>,
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOf(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
return getRealStatus(status, name, floorId);
|
||||
}
|
||||
|
||||
function getRealStatus(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId: FloorIds = core.status.floorId
|
||||
): any {
|
||||
const { getSkillLevel } = Mota.Plugin.require('skillTree_g');
|
||||
if (name instanceof Array) {
|
||||
return Object.fromEntries(
|
||||
name.map(v => [v, getRealStatus(status, v, floorId)])
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'all') {
|
||||
return Object.fromEntries(
|
||||
Object.keys(core.status.hero).map(v => [
|
||||
v,
|
||||
v !== 'all' &&
|
||||
getRealStatus(status, v as keyof HeroStatus, floorId)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
let s = (status?.[name] ?? core.status.hero[name]) as number;
|
||||
if (s === null || s === void 0) {
|
||||
throw new ReferenceError(
|
||||
`Wrong hero status property name is delivered: ${name}`
|
||||
);
|
||||
}
|
||||
|
||||
// 永夜、极昼
|
||||
if (name === 'atk' || name === 'def') {
|
||||
s += window.flags?.[`night_${floorId}`] ?? 0;
|
||||
}
|
||||
|
||||
// 技能
|
||||
if (flags.bladeOn && flags.blade) {
|
||||
const level = getSkillLevel(2);
|
||||
if (name === 'atk') {
|
||||
s *= 1 + 0.1 * level;
|
||||
}
|
||||
if (name === 'def') {
|
||||
s *= 1 - 0.1 * level;
|
||||
}
|
||||
}
|
||||
if (flags.shield && flags.shieldOn) {
|
||||
const level = getSkillLevel(10);
|
||||
if (name === 'atk') {
|
||||
s *= 1 - 0.1 * level;
|
||||
}
|
||||
if (name === 'def') {
|
||||
s *= 1 + 0.1 * level;
|
||||
}
|
||||
}
|
||||
|
||||
// buff
|
||||
if (typeof s === 'number')
|
||||
s *= core.getBuff(name as keyof NumbericHeroStatus);
|
||||
|
||||
// 取整
|
||||
if (typeof s === 'number') s = Math.floor(s);
|
||||
return s;
|
||||
}
|
@ -6,7 +6,7 @@ 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 './hero';
|
||||
import * as hero from './state/hero';
|
||||
import * as miscMechanism from './mechanism/misc';
|
||||
import * as study from './mechanism/study';
|
||||
|
||||
|
474
src/game/state/hero.ts
Normal file
474
src/game/state/hero.ts
Normal file
@ -0,0 +1,474 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep, isNil } from 'lodash-es';
|
||||
import { GameState, ISerializable } from './state';
|
||||
import { ItemState } from './item';
|
||||
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus;
|
||||
export function getHeroStatusOn(
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOn<K extends keyof HeroStatus>(
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOn(
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
// @ts-ignore
|
||||
return getHeroStatusOf(core.status.hero, name, floorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一定状态下的勇士在某一点的属性
|
||||
* @param status 勇士的状态
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: 'all',
|
||||
floorId?: FloorIds
|
||||
): HeroStatus;
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOf<K extends keyof HeroStatus>(
|
||||
status: Partial<HeroStatus>,
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOf(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
return getRealStatus(status, name, floorId);
|
||||
}
|
||||
|
||||
function getRealStatus(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId: FloorIds = core.status.floorId
|
||||
): any {
|
||||
const { getSkillLevel } = Mota.Plugin.require('skillTree_g');
|
||||
if (name instanceof Array) {
|
||||
return Object.fromEntries(
|
||||
name.map(v => [v, getRealStatus(status, v, floorId)])
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'all') {
|
||||
return Object.fromEntries(
|
||||
Object.keys(core.status.hero).map(v => [
|
||||
v,
|
||||
v !== 'all' &&
|
||||
getRealStatus(status, v as keyof HeroStatus, floorId)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
let s = (status?.[name] ?? core.status.hero[name]) as number;
|
||||
if (s === null || s === void 0) {
|
||||
throw new ReferenceError(
|
||||
`Wrong hero status property name is delivered: ${name}`
|
||||
);
|
||||
}
|
||||
|
||||
// 永夜、极昼
|
||||
if (name === 'atk' || name === 'def') {
|
||||
s += window.flags?.[`night_${floorId}`] ?? 0;
|
||||
}
|
||||
|
||||
// 技能
|
||||
if (flags.bladeOn && flags.blade) {
|
||||
const level = getSkillLevel(2);
|
||||
if (name === 'atk') {
|
||||
s *= 1 + 0.1 * level;
|
||||
}
|
||||
if (name === 'def') {
|
||||
s *= 1 - 0.1 * level;
|
||||
}
|
||||
}
|
||||
if (flags.shield && flags.shieldOn) {
|
||||
const level = getSkillLevel(10);
|
||||
if (name === 'atk') {
|
||||
s *= 1 - 0.1 * level;
|
||||
}
|
||||
if (name === 'def') {
|
||||
s *= 1 + 0.1 * level;
|
||||
}
|
||||
}
|
||||
|
||||
// buff
|
||||
if (typeof s === 'number')
|
||||
s *= core.getBuff(name as keyof NumbericHeroStatus);
|
||||
|
||||
// 取整
|
||||
if (typeof s === 'number') s = Math.floor(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
export interface IHeroStatusDefault {
|
||||
atk: number;
|
||||
def: number;
|
||||
hp: number;
|
||||
}
|
||||
|
||||
interface HeroStateEvent {
|
||||
set: [key: string | number | symbol, value: any];
|
||||
}
|
||||
|
||||
type HeroStatusCalculate<T> = <K extends keyof T>(key: K, value: T[K]) => T[K];
|
||||
|
||||
export class HeroState<
|
||||
T extends object = IHeroStatusDefault
|
||||
> extends EventEmitter<HeroStateEvent> {
|
||||
readonly status: T;
|
||||
readonly computedStatus: T;
|
||||
|
||||
readonly buffable: Set<keyof T> = new Set();
|
||||
readonly buffMap: Map<keyof T, number> = new Map();
|
||||
|
||||
private cal: HeroStatusCalculate<T> = (_, value) => value;
|
||||
|
||||
constructor(init: T) {
|
||||
super();
|
||||
this.status = init;
|
||||
this.computedStatus = cloneDeep(init);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置某个属性的值
|
||||
* @param key 要设置的属性
|
||||
* @param value 属性值
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
setStatus<K extends keyof T>(key: K, value: T[K]): boolean {
|
||||
this.status[key] = value;
|
||||
this.emit('set', key, value);
|
||||
return this.refreshStatus(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加或减少一个属性的值,只对数字型的属性有效
|
||||
* @param key 要修改的属性
|
||||
* @param value 属性的增量
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
addStatus<K extends SelectKey<T, number>>(key: K, value: number): boolean {
|
||||
if (typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
14,
|
||||
`Cannot add status of non-number status. Key: ${String(key)}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return this.setStatus<K>(key, (this.status[key] + value) as T[K]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个属性的原始值
|
||||
* @param key 要获取的属性
|
||||
* @returns 属性的值
|
||||
*/
|
||||
getStatus<K extends keyof T>(key: K): T[K] {
|
||||
return this.status[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个属性计算后的值,也就是2.x所说的勇士真实属性
|
||||
* @param key 要获取的属性值
|
||||
*/
|
||||
getComputedStatus<K extends keyof T>(key: K): T[K] {
|
||||
return this.computedStatus[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记某个属性为可以被buff加成
|
||||
*/
|
||||
markBuffable(key: SelectKey<T, number>): void {
|
||||
if (typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
12,
|
||||
`Cannot mark buffable with a non-number status. Key: ${String(
|
||||
key
|
||||
)}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.buffable.add(key);
|
||||
this.buffMap.set(key, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置某个属性的buff值
|
||||
* @param key 要设置buff的属性
|
||||
* @param value buff值
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
setBuff(key: SelectKey<T, number>, value: number): boolean {
|
||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
13,
|
||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
this.buffMap.set(key, value);
|
||||
return this.refreshStatus(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加或减少一个buff值
|
||||
* @param key 要修改的buff属性
|
||||
* @param value buff增量
|
||||
* @returns 是否修改成功
|
||||
*/
|
||||
addBuff(key: SelectKey<T, number>, value: number): boolean {
|
||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
13,
|
||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return this.setBuff(key, this.buffMap.get(key)! + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新某个或所有属性,重新进行计算
|
||||
* @param key 要刷新的属性名,不填表示刷新所有属性
|
||||
* @returns 是否计算成功
|
||||
*/
|
||||
refreshStatus(key?: keyof T): boolean {
|
||||
if (key === void 0) {
|
||||
for (const [key, value] of Object.entries(this.status)) {
|
||||
// @ts-ignore
|
||||
this.computedStatus[key] = this.cal(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.computedStatus[key] = this.cal(key, this.status[key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算所有可以buff加成的属性
|
||||
* @returns 是否计算成功
|
||||
*/
|
||||
refreshBuffable(): boolean {
|
||||
for (const key of this.buffable) {
|
||||
this.computedStatus[key] = this.cal(key, this.status[key]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
|
||||
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
|
||||
*/
|
||||
overrideCalculate(fn: HeroStatusCalculate<T>) {
|
||||
this.cal = fn;
|
||||
}
|
||||
}
|
||||
|
||||
interface IHeroItem {
|
||||
items: Map<AllIdsOf<'items'>, number>;
|
||||
|
||||
/**
|
||||
* 设置勇士拥有的物品数量
|
||||
* @param item 物品id
|
||||
* @param value 物品数量
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
setItem(item: AllIdsOf<'items'>, value: number): boolean;
|
||||
|
||||
/**
|
||||
* 增加或减少勇士拥有的物品数量
|
||||
* @param item 物品id
|
||||
* @param value 物品数量增量
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
addItem(item: AllIdsOf<'items'>, value: number): boolean;
|
||||
|
||||
/**
|
||||
* 使用一个物品
|
||||
* @param item 物品id
|
||||
* @returns 是否使用成功
|
||||
*/
|
||||
useItem(item: AllIdsOf<'items'>, x?: number, y?: number): boolean;
|
||||
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(item: AllIdsOf<'items'>, num: number): void;
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id
|
||||
* @param x 物品所在x坐标
|
||||
* @param y 物品所在y坐标
|
||||
* @param floorId 物品所在楼层
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(
|
||||
item: AllIdsOf<'items'>,
|
||||
x: number,
|
||||
y: number,
|
||||
floorId?: FloorIds,
|
||||
num?: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 获取某个物品的数量
|
||||
* @param item 物品id
|
||||
*/
|
||||
itemCount(item: AllIdsOf<'items'>): number;
|
||||
|
||||
/**
|
||||
* 判断勇士是否拥有这个物品
|
||||
* @param item 物品id
|
||||
*/
|
||||
hasItem(item: AllIdsOf<'items'>): boolean;
|
||||
}
|
||||
|
||||
interface HeroEvent {
|
||||
beforeMove: [dir: Dir2];
|
||||
afterMove: [dir: Dir2];
|
||||
beforeMoveDirectly: [x: number, y: number];
|
||||
afterMoveDirectly: [x: number, y: number];
|
||||
stateChange: [state: HeroState<any>];
|
||||
}
|
||||
|
||||
export class Hero<T extends object = IHeroStatusDefault>
|
||||
extends EventEmitter<HeroEvent>
|
||||
implements IHeroItem, ISerializable
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
floorId: FloorIds;
|
||||
id: string;
|
||||
|
||||
state: HeroState<T>;
|
||||
items: Map<AllIdsOf<'items'>, number> = new Map();
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
floorId: FloorIds,
|
||||
state: HeroState<T>,
|
||||
gameState: GameState
|
||||
) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.floorId = floorId;
|
||||
this.state = state;
|
||||
|
||||
const list = gameState.state.hero.list;
|
||||
if (list.has(id)) {
|
||||
logger.warn(11, `Repeated hero: ${id}.`);
|
||||
}
|
||||
list.set(id, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置勇士状态,效果为直接将一个状态替换为另一个状态,不应当经常使用,仅应当在不得不使用的时候使用
|
||||
* @param state 要设置为的勇士状态
|
||||
*/
|
||||
setState(state: HeroState<T>): void {
|
||||
this.state = state;
|
||||
this.emit('stateChange', state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取勇士状态
|
||||
*/
|
||||
getState(): HeroState<T> {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 见 {@link HeroState.refreshStatus}
|
||||
*/
|
||||
refreshState(key?: keyof T) {
|
||||
return this.state.refreshStatus(key);
|
||||
}
|
||||
|
||||
setItem(item: AllIdsOf<'items'>, value: number): boolean {
|
||||
this.items.set(item, value < 0 ? 0 : value);
|
||||
return true;
|
||||
}
|
||||
|
||||
addItem(item: AllIdsOf<'items'>, value: number): boolean {
|
||||
return this.setItem(item, (this.items.get(item) ?? 0) + value);
|
||||
}
|
||||
|
||||
useItem(item: AllIdsOf<'items'>): boolean {
|
||||
const state = ItemState.item(item);
|
||||
return !!state?.use(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(item: AllIdsOf<'items'>, num: number): boolean;
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id,填写坐标时无效
|
||||
* @param x 物品所在x坐标
|
||||
* @param y 物品所在y坐标
|
||||
* @param floorId 物品所在楼层
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(x: number, y: number, floorId?: FloorIds, num?: number): boolean;
|
||||
getItem(
|
||||
item: AllIdsOf<'items'> | number,
|
||||
y: number,
|
||||
floorId: FloorIds = this.floorId,
|
||||
num: number = 1
|
||||
): boolean {
|
||||
if (!isNil(floorId) && typeof item === 'number') {
|
||||
// 如果指定了坐标
|
||||
const block = core.getBlock(item as number, y, floorId);
|
||||
const id = block.event.id as AllIdsOf<'items'>;
|
||||
const cls = core.material.items[id]?.cls;
|
||||
if (cls === void 0) {
|
||||
logger.warn(
|
||||
15,
|
||||
`Cannot get item of a non-item block on loc: ${item},${y},${floorId}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return this.addItem(id, num!);
|
||||
}
|
||||
return this.addItem(item as AllIdsOf<'items'>, num!);
|
||||
}
|
||||
|
||||
itemCount(item: AllIdsOf<'items'>): number {
|
||||
return this.items.get(item) ?? 0;
|
||||
}
|
||||
|
||||
hasItem(item: AllIdsOf<'items'>): boolean {
|
||||
return this.itemCount(item) > 0;
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
145
src/game/state/item.ts
Normal file
145
src/game/state/item.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import type { Hero } from './hero';
|
||||
import { GameState, gameStates, IGameState } from './state';
|
||||
import { loading } from '../game';
|
||||
|
||||
type EffectFn = (state: IGameState, hero: Hero<any>) => void;
|
||||
type CanUseEffectFn = (state: IGameState, hero: Hero<any>) => boolean;
|
||||
|
||||
interface ItemStateEvent {
|
||||
use: [hero: Hero<any>];
|
||||
}
|
||||
|
||||
export class ItemState<
|
||||
I extends AllIdsOf<'items'> = AllIdsOf<'items'>
|
||||
> extends EventEmitter<ItemStateEvent> {
|
||||
static items: Map<AllIdsOf<'items'>, ItemState> = new Map();
|
||||
|
||||
id: I;
|
||||
cls: ItemClsOf<I>;
|
||||
name: string;
|
||||
text?: string;
|
||||
hideInToolBox: boolean;
|
||||
hideInReplay: boolean;
|
||||
|
||||
/** 即捡即用效果 */
|
||||
itemEffect?: string;
|
||||
/** 即捡即用道具捡过之后的提示 */
|
||||
itemEffectTip?: string;
|
||||
/** 使用道具时执行的事件 */
|
||||
useItemEvent?: MotaEvent;
|
||||
/** 使用道具时执行的代码 */
|
||||
useItemEffect?: string;
|
||||
/** 能否使用道具 */
|
||||
canUseItemEffect?: string | boolean;
|
||||
|
||||
private noRoute: boolean = false;
|
||||
|
||||
itemEffectFn?: EffectFn;
|
||||
useItemEffectFn?: EffectFn;
|
||||
canUseItemEffectFn?: CanUseEffectFn;
|
||||
|
||||
constructor(id: I) {
|
||||
super();
|
||||
const items = items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a;
|
||||
this.id = id;
|
||||
const item = items[id];
|
||||
this.cls = item.cls;
|
||||
this.name = item.name;
|
||||
this.text = item.text;
|
||||
this.hideInToolBox = item.hideInToolBox;
|
||||
this.hideInReplay = item.hideInReplay;
|
||||
this.itemEffect = item.itemEffect;
|
||||
this.itemEffectTip = item.itemEffectTip;
|
||||
this.useItemEvent = item.useItemEvent;
|
||||
this.useItemEffect = item.useItemEffect;
|
||||
this.canUseItemEffect = item.canUseItemEffect;
|
||||
|
||||
this.compileFunction();
|
||||
this.compileEvent();
|
||||
}
|
||||
|
||||
private compileFunction() {
|
||||
if (this.itemEffect) {
|
||||
this.itemEffectFn = new Function(
|
||||
`state`,
|
||||
this.itemEffect
|
||||
) as EffectFn;
|
||||
}
|
||||
if (this.useItemEffect) {
|
||||
this.useItemEffectFn = new Function(
|
||||
`state`,
|
||||
this.useItemEffect
|
||||
) as EffectFn;
|
||||
}
|
||||
if (this.canUseItemEffect) {
|
||||
if (typeof this.canUseItemEffect === 'boolean') {
|
||||
this.canUseItemEffectFn = () =>
|
||||
this.canUseItemEffect as boolean;
|
||||
} else {
|
||||
this.useItemEffectFn = new Function(
|
||||
`state`,
|
||||
this.canUseItemEffect
|
||||
) as CanUseEffectFn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private compileEvent() {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用这个物品
|
||||
* @param state 游戏状态
|
||||
* @param num 使用的数量,仅对tools和items有效
|
||||
*/
|
||||
use(hero: Hero<any>): boolean {
|
||||
if (!this.canUse(hero)) return false;
|
||||
if (!gameStates.now) return false;
|
||||
const state = gameStates.now.state;
|
||||
this.useItemEffectFn?.(state, hero);
|
||||
if (this.useItemEvent) core.insertAction(this.useItemEvent);
|
||||
if (!this.noRoute) state.route.push(`item:${this.id}`);
|
||||
|
||||
hero.addItem(this.id, -1);
|
||||
this.emit('use', hero);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以使用一个物品
|
||||
* @param hero 使用物品的勇士
|
||||
*/
|
||||
canUse(hero: Hero<any>, num: number = 1): boolean {
|
||||
if (num <= 0) return false;
|
||||
if (hero.itemCount(this.id) < num) return false;
|
||||
if (!gameStates.now) return false;
|
||||
return !!this.canUseItemEffectFn?.(gameStates.now.state, hero);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记当前物品为不进入录像,也就是录像中不会使用该道具
|
||||
*/
|
||||
markNoRoute() {
|
||||
this.noRoute = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个道具的信息
|
||||
* @param id 要获取的道具id
|
||||
*/
|
||||
static item<I extends AllIdsOf<'items'>>(id: I): ItemState<I> | undefined {
|
||||
return this.items.get(id) as ItemState<I>;
|
||||
}
|
||||
}
|
||||
|
||||
loading.once('coreInit', () => {
|
||||
for (const key of Object.keys(items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a)) {
|
||||
ItemState.items.set(
|
||||
key as AllIdsOf<'items'>,
|
||||
new ItemState(key as AllIdsOf<'items'>)
|
||||
);
|
||||
}
|
||||
});
|
91
src/game/state/state.ts
Normal file
91
src/game/state/state.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { Undoable } from '@/core/interface';
|
||||
import { Hero, HeroState } from './hero';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export interface ISerializable {
|
||||
toJSON(): string;
|
||||
}
|
||||
|
||||
export interface IGameState {
|
||||
hero: MonoStore<Hero<any>>;
|
||||
route: string[];
|
||||
}
|
||||
|
||||
export class GameState implements ISerializable {
|
||||
state: IGameState;
|
||||
|
||||
constructor(state: IGameState) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return '';
|
||||
}
|
||||
|
||||
static loadState(state: GameState) {}
|
||||
|
||||
static fromJSON(json: string) {}
|
||||
|
||||
static loadStateFromJSON(json: string) {}
|
||||
}
|
||||
|
||||
interface MonoStoreEvent<T> {
|
||||
change: [before: T | undefined, after: T | undefined];
|
||||
}
|
||||
|
||||
export class MonoStore<T extends ISerializable>
|
||||
extends EventEmitter<MonoStoreEvent<T>>
|
||||
implements ISerializable
|
||||
{
|
||||
list: Map<string, T> = new Map();
|
||||
using?: T;
|
||||
|
||||
use(id: string) {
|
||||
const before = this.using;
|
||||
this.using = this.list.get(id);
|
||||
this.emit('change', before, this.using);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
interface StateStoreEvent {
|
||||
undo: [state: GameState];
|
||||
redo: [state: GameState];
|
||||
change: [before: GameState | undefined, now: GameState];
|
||||
}
|
||||
|
||||
class StateStore
|
||||
extends EventEmitter<StateStoreEvent>
|
||||
implements Undoable<GameState>
|
||||
{
|
||||
now?: GameState;
|
||||
stack: GameState[] = [];
|
||||
redoStack: GameState[] = [];
|
||||
|
||||
undo(): GameState | undefined {
|
||||
const state = this.stack.pop();
|
||||
if (!state) return void 0;
|
||||
this.redoStack.push(state);
|
||||
this.emit('undo', state);
|
||||
return state;
|
||||
}
|
||||
|
||||
redo(): GameState | undefined {
|
||||
const state = this.redoStack.pop();
|
||||
if (!state) return void 0;
|
||||
this.stack.push(state);
|
||||
this.emit('redo', state);
|
||||
return state;
|
||||
}
|
||||
|
||||
use(state: GameState) {
|
||||
const before = this.now;
|
||||
this.now = state;
|
||||
this.emit('change', before, state);
|
||||
}
|
||||
}
|
||||
|
||||
export const gameStates = new StateStore();
|
@ -20,7 +20,7 @@ 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';
|
||||
import type * as hero from './hero';
|
||||
import type * as hero from './state/hero';
|
||||
import type * as damage from './enemy/damage';
|
||||
import type { Logger } from '@/core/common/logger';
|
||||
import type { Danmaku } from '@/core/main/custom/danmaku';
|
||||
|
4
src/types/action.d.ts
vendored
4
src/types/action.d.ts
vendored
@ -94,6 +94,7 @@ interface Actions extends VoidedActionFuncs {
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 此函数将注册一个用户交互行为。
|
||||
* @param action 要注册的交互类型
|
||||
* @param name 自定义名称,可被注销使用
|
||||
@ -108,6 +109,7 @@ interface Actions extends VoidedActionFuncs {
|
||||
): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 注销一个用户交互行为
|
||||
* @param action 要注销的交互类型
|
||||
* @param name 要注销的自定义名称
|
||||
@ -115,6 +117,7 @@ interface Actions extends VoidedActionFuncs {
|
||||
unregisterAction(action: ActionKey, name: string): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 执行一个用户交互行为
|
||||
*/
|
||||
doRegisteredAction<K extends ActionKey>(
|
||||
@ -123,6 +126,7 @@ interface Actions extends VoidedActionFuncs {
|
||||
): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 判断一个横坐标是否在(_HX_ - 2, _HX_ + 2)范围外
|
||||
* @param x 要判断的横坐标
|
||||
*/
|
||||
|
4
src/types/control.d.ts
vendored
4
src/types/control.d.ts
vendored
@ -407,11 +407,13 @@ interface Control {
|
||||
addGameCanvasTranslate(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 更新大地图的可见区域
|
||||
*/
|
||||
updateViewport(): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 设置视野范围
|
||||
* @param px 相对大地图左上角的偏移横坐标,单位像素
|
||||
* @param py 相对大地图左上角的偏移纵坐标,单位像素
|
||||
@ -419,6 +421,7 @@ interface Control {
|
||||
setViewport(px?: number, py?: number): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 移动视野范围,这东西真的有人用吗...高级动画 + setViewport就完事了(
|
||||
* @param x 移动的横坐标,单位格子
|
||||
* @param y 移动的纵坐标,单位格子
|
||||
@ -481,6 +484,7 @@ interface Control {
|
||||
updateDamage(floorId?: FloorIds, ctx?: CtxRefer): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 重绘地图显伤
|
||||
* @param ctx 绘制到的画布
|
||||
*/
|
||||
|
10
src/types/core.d.ts
vendored
10
src/types/core.d.ts
vendored
@ -1261,11 +1261,6 @@ interface Main extends MainData {
|
||||
*/
|
||||
readonly __VERSION_CODE__: number;
|
||||
|
||||
readonly RESOURCE_INDEX: Record<string, string>;
|
||||
readonly RESOURCE_URL: string;
|
||||
readonly RESOURCE_SYMBOL: string;
|
||||
readonly RESOURCE_TYPE: 'dev' | 'dist' | 'gh' | 'local';
|
||||
|
||||
/**
|
||||
* 初始化游戏
|
||||
* @param mode 初始化游戏的模式,游玩还是编辑器
|
||||
@ -1316,6 +1311,7 @@ interface Main extends MainData {
|
||||
): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 设置加载界面的加载提示文字
|
||||
*/
|
||||
setMainTipsText(text: string): void;
|
||||
@ -1334,23 +1330,27 @@ interface Main extends MainData {
|
||||
createOnChoiceAnimation(): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 选中开始界面的一个按钮
|
||||
* @param index 要选中的按钮
|
||||
*/
|
||||
selectButton(index: number): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 加载一系列字体
|
||||
* @param fonts 要加载的字体列表
|
||||
*/
|
||||
importFonts(fonts: FontIds[]): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 执行样板的所有监听
|
||||
*/
|
||||
listen(): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 执行ts的插件转发
|
||||
*/
|
||||
forward(): void;
|
||||
|
Loading…
Reference in New Issue
Block a user