mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-02 17:44:31 +08:00
chore: 地图与素材配置等改为样板内容
This commit is contained in:
1 parent
5de5153441
commit
6ace764dc9
219 files changed
+402
-24733
No files matched your search
@@ -2,7 +2,6 @@ import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
|
||||
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { hook } from '@user/data-base';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
import {
|
||||
EnemyInfo,
|
||||
DamageInfo,
|
||||
@@ -285,11 +284,6 @@ export class DamageEnemy implements IDamageEnemy {
|
||||
info.atk += flags[`inte_${floorId}`] ?? 0;
|
||||
}
|
||||
|
||||
// 极昼永夜
|
||||
const night = NightSpecial.getNight(floorId);
|
||||
info.atk -= night;
|
||||
info.def -= night;
|
||||
|
||||
// 融化,融化不属于怪物光环,因此不能用provide和inject计算,需要在这里计算
|
||||
const melt = flags[`melt_${floorId}`];
|
||||
if (!isNil(melt) && !isNil(this.x) && !isNil(this.y)) {
|
||||
@@ -720,27 +714,9 @@ export class DamageEnemy implements IDamageEnemy {
|
||||
|
||||
private calEnemyDamageOf(hero: Partial<HeroStatus>, enemy: UserEnemyInfo) {
|
||||
const status = getHeroStatusOf(hero, realStatus, this.floorId);
|
||||
let damage = calDamageWith(enemy, status) ?? Infinity;
|
||||
let bestSkill = -1;
|
||||
const damage = calDamageWith(enemy, status) ?? Infinity;
|
||||
|
||||
// 自动切换技能
|
||||
if (HeroSkill.getAutoSkill()) {
|
||||
for (const skill of skills) {
|
||||
if (!HeroSkill.learnedSkill(skill)) continue;
|
||||
HeroSkill.enableSkill(skill);
|
||||
const status = getHeroStatusOf(hero, realStatus);
|
||||
|
||||
const d = calDamageWith(enemy, status) ?? Infinity;
|
||||
|
||||
if (d < damage) {
|
||||
damage = d;
|
||||
bestSkill = skill;
|
||||
}
|
||||
HeroSkill.disableSkill();
|
||||
}
|
||||
}
|
||||
|
||||
return { damage, skill: bestSkill };
|
||||
return { damage };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -928,10 +904,6 @@ const realStatus: (keyof HeroStatus)[] = [
|
||||
'mana',
|
||||
'magicDef'
|
||||
];
|
||||
/**
|
||||
* 主动技能列表
|
||||
*/
|
||||
const skills: HeroSkill.Skill[] = [HeroSkill.Blade, HeroSkill.Shield];
|
||||
|
||||
/**
|
||||
* 计算怪物伤害
|
||||
|
||||
@@ -1,9 +1,2 @@
|
||||
import { createMechanism } from './mechanism';
|
||||
|
||||
export function create() {
|
||||
createMechanism();
|
||||
}
|
||||
|
||||
export * from './enemy';
|
||||
export * from './mechanism';
|
||||
export * from './state';
|
||||
@@ -1,4 +0,0 @@
|
||||
export function createMechanism() {}
|
||||
|
||||
export * from './misc';
|
||||
export * from './skillTree';
|
||||
@@ -1,276 +0,0 @@
|
||||
import { backDir, has } from '@user/data-utils';
|
||||
import { loading } from '@user/data-base';
|
||||
import type { LayerDoorAnimate } from '@user/client-modules';
|
||||
|
||||
/**
|
||||
* 一些零散机制的数据
|
||||
*/
|
||||
export namespace MiscData {
|
||||
/** 循环式地图 */
|
||||
export const loopMaps: Set<FloorIds> = new Set(['tower6']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 永夜/极昼
|
||||
*/
|
||||
export namespace NightSpecial {
|
||||
const nightMap = new Map<FloorIds, number>();
|
||||
|
||||
export function getNight(floor: FloorIds) {
|
||||
return nightMap.get(floor) ?? 0;
|
||||
}
|
||||
|
||||
export function addNight(floor: FloorIds, value: number) {
|
||||
const num = nightMap.get(floor) ?? 0;
|
||||
nightMap.set(floor, num + value);
|
||||
}
|
||||
|
||||
export function clearNight(floors: Iterable<FloorIds>) {
|
||||
for (const floor of floors) {
|
||||
nightMap.delete(floor);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveNight() {
|
||||
return nightMap.entries();
|
||||
}
|
||||
|
||||
export function loadNight(night: Iterable<[FloorIds, number]>) {
|
||||
nightMap.clear();
|
||||
for (const [floor, num] of night) {
|
||||
nightMap.set(floor, num);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAll() {
|
||||
return nightMap;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace BluePalace {
|
||||
type DoorConvertInfo = [id: AllIds, x: number, y: number];
|
||||
|
||||
// ---------- 黄蓝门转换
|
||||
|
||||
export function doorConvert(
|
||||
x?: number,
|
||||
y?: number,
|
||||
floorId: FloorIds = core.status.floorId
|
||||
) {
|
||||
core.autosave();
|
||||
core.extractBlocks(floorId);
|
||||
const blocks = core.status.maps[floorId].blocks;
|
||||
|
||||
const toConvert: DoorConvertInfo[] = [];
|
||||
blocks.forEach(v => {
|
||||
if (v.id === 492) {
|
||||
core.setBlock(494, v.x, v.y, floorId);
|
||||
toConvert.push(['A492', v.x, v.y]);
|
||||
} else if (v.id === 494) {
|
||||
core.setBlock(492, v.x, v.y, floorId);
|
||||
toConvert.push(['A494', v.x, v.y]);
|
||||
}
|
||||
});
|
||||
|
||||
if (has(x) && has(y)) {
|
||||
core.removeBlock(x, y, floorId);
|
||||
}
|
||||
|
||||
if (core.isReplaying() || core.status.floorId !== floorId) {
|
||||
core.doAction();
|
||||
return;
|
||||
}
|
||||
|
||||
core.lockControl();
|
||||
core.playSound('door.opus');
|
||||
|
||||
const Adapter = Mota.require('@motajs/render').RenderAdapter;
|
||||
const adapter = Adapter.get<LayerDoorAnimate>('door-animate');
|
||||
const texture = Mota.require('@user/client-modules').texture;
|
||||
if (adapter) {
|
||||
Promise.all(
|
||||
toConvert.map(v => {
|
||||
const block = core.initBlock(
|
||||
v[1],
|
||||
v[2],
|
||||
texture.idNumberMap[v[0]]
|
||||
);
|
||||
return adapter.all('openDoor', block);
|
||||
})
|
||||
).then(() => {
|
||||
core.unlockControl();
|
||||
core.doAction();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 传送门部分
|
||||
|
||||
export interface Portal {
|
||||
fx: number;
|
||||
fy: number;
|
||||
dir: Dir;
|
||||
tx: number;
|
||||
ty: number;
|
||||
toDir: Dir;
|
||||
}
|
||||
|
||||
export interface PortalTo {
|
||||
x: number;
|
||||
y: number;
|
||||
dir: Dir;
|
||||
}
|
||||
|
||||
type PortalMap = Map<FloorIds, Map<number, Partial<Record<Dir, PortalTo>>>>;
|
||||
|
||||
export const portalMap: PortalMap = new Map();
|
||||
|
||||
export const portals: Partial<Record<FloorIds, Portal[]>> = {
|
||||
MT76: [
|
||||
{ fx: 11, fy: 7, dir: 'right', tx: 4, ty: 6, toDir: 'down' },
|
||||
{ fx: 6, fy: 5, dir: 'left', tx: 8, ty: 13, toDir: 'right' }
|
||||
],
|
||||
MT77: [
|
||||
{ fx: 2, fy: 9, dir: 'up', tx: 10, ty: 13, toDir: 'right' },
|
||||
{ fx: 10, fy: 8, dir: 'right', tx: 3, ty: 0, toDir: 'down' },
|
||||
{ fx: 1, fy: 0, dir: 'down', tx: 8, ty: 1, toDir: 'left' }
|
||||
],
|
||||
MT78: [
|
||||
{ fx: 8, fy: 4, dir: 'right', tx: 8, ty: 6, toDir: 'left' },
|
||||
{ fx: 7, fy: 7, dir: 'up', tx: 1, ty: 0, toDir: 'down' }
|
||||
],
|
||||
MT79: [
|
||||
{ fx: 5, fy: 10, dir: 'right', tx: 9, ty: 7, toDir: 'left' },
|
||||
{ fx: 2, fy: 2, dir: 'up', tx: 7, ty: 5, toDir: 'down' },
|
||||
{ fx: 4, fy: 11, dir: 'up', tx: 5, ty: 7, toDir: 'right' },
|
||||
{ fx: 7, fy: 11, dir: 'down', tx: 7, ty: 9, toDir: 'up' }
|
||||
],
|
||||
MT80: [
|
||||
{ fx: 2, fy: 10, dir: 'right', tx: 1, ty: 2, toDir: 'down' },
|
||||
{ fx: 2, fy: 10, dir: 'left', tx: 13, ty: 5, toDir: 'up' }
|
||||
],
|
||||
MT81: [
|
||||
{ fx: 4, fy: 8, dir: 'right', tx: 1, ty: 11, toDir: 'down' },
|
||||
{ fx: 7, fy: 13, dir: 'right', tx: 13, ty: 5, toDir: 'up' }
|
||||
],
|
||||
MT82: [{ fx: 9, fy: 10, dir: 'left', tx: 6, ty: 5, toDir: 'left' }],
|
||||
MT83: [
|
||||
{ fx: 5, fy: 11, dir: 'left', tx: 9, ty: 11, toDir: 'right' },
|
||||
{ fx: 5, fy: 3, dir: 'left', tx: 9, ty: 3, toDir: 'right' },
|
||||
{ fx: 2, fy: 2, dir: 'up', tx: 2, ty: 12, toDir: 'down' },
|
||||
{ fx: 12, fy: 2, dir: 'up', tx: 12, ty: 12, toDir: 'down' }
|
||||
],
|
||||
MT84: [
|
||||
{ fx: 2, fy: 3, dir: 'right', tx: 12, ty: 3, toDir: 'left' },
|
||||
{ fx: 2, fy: 11, dir: 'right', tx: 12, ty: 11, toDir: 'left' }
|
||||
],
|
||||
MT94: [{ fx: 12, fy: 11, dir: 'left', tx: 5, ty: 1, toDir: 'left' }],
|
||||
MT95: [
|
||||
{ fx: 13, fy: 14, dir: 'up', tx: 7, ty: 8, toDir: 'left' },
|
||||
{ fx: 0, fy: 1, dir: 'right', tx: 14, ty: 1, toDir: 'left' },
|
||||
{ fx: 6, fy: 13, dir: 'right', tx: 6, ty: 0, toDir: 'down' }
|
||||
],
|
||||
MT96: [{ fx: 6, fy: 11, dir: 'down', tx: 4, ty: 14, toDir: 'up' }],
|
||||
MT97: [{ fx: 0, fy: 1, dir: 'right', tx: 8, ty: 9, toDir: 'right' }]
|
||||
};
|
||||
loading.once('coreInit', initPortals);
|
||||
|
||||
function generatePortalMap() {
|
||||
const delta: Record<Dir, [[number, number], [number, number]]> = {
|
||||
// 方向:[正向, 逆向]<进出>
|
||||
left: [
|
||||
[0, 0],
|
||||
[-1, 0]
|
||||
],
|
||||
down: [
|
||||
[0, 0],
|
||||
[0, 1]
|
||||
],
|
||||
right: [
|
||||
[0, 0],
|
||||
[1, 0]
|
||||
],
|
||||
up: [
|
||||
[0, 0],
|
||||
[0, -1]
|
||||
]
|
||||
};
|
||||
for (const [floor, p] of Object.entries(portals)) {
|
||||
const width = core.floors[floor as FloorIds].width;
|
||||
const map = new Map<number, Partial<Record<Dir, PortalTo>>>();
|
||||
portalMap.set(floor as FloorIds, map);
|
||||
|
||||
// 正向映射
|
||||
p.forEach(v => {
|
||||
const [[fdx, fdy], [tdx, tdy]] = delta[v.dir];
|
||||
const [[toFdx, toFdy], [toTdx, toTdy]] =
|
||||
delta[backDir(v.toDir)];
|
||||
const fx = v.fx + fdx;
|
||||
const fy = v.fy + fdy;
|
||||
const tx = v.fx + tdx;
|
||||
const ty = v.fy + tdy;
|
||||
const index = fx + fy * width;
|
||||
const backIndex = tx + ty * width;
|
||||
if (index < 0 || backIndex < 0) return;
|
||||
let data = map.get(index);
|
||||
let backData = map.get(backIndex);
|
||||
if (!data) {
|
||||
data = {};
|
||||
map.set(index, data);
|
||||
}
|
||||
if (!backData) {
|
||||
backData = {};
|
||||
map.set(backIndex, backData);
|
||||
}
|
||||
|
||||
data[v.dir] = {
|
||||
x: v.tx + toFdx,
|
||||
y: v.ty + toFdy,
|
||||
dir: backDir(v.toDir)
|
||||
};
|
||||
backData[backDir(v.dir)] = {
|
||||
x: v.tx - toTdx,
|
||||
y: v.ty - toTdy,
|
||||
dir: v.toDir
|
||||
};
|
||||
});
|
||||
// 逆向映射
|
||||
p.forEach(v => {
|
||||
const [[fdx, fdy], [tdx, tdy]] = delta[backDir(v.toDir)];
|
||||
const [[toFdx, toFdy], [toTdx, toTdy]] = delta[v.dir];
|
||||
const fx = v.tx - fdx;
|
||||
const fy = v.ty - fdy;
|
||||
const tx = v.tx - tdx;
|
||||
const ty = v.ty - tdy;
|
||||
const index = fx + fy * width;
|
||||
const backIndex = tx + ty * width;
|
||||
|
||||
let data = map.get(index);
|
||||
let backData = map.get(backIndex);
|
||||
if (!data) {
|
||||
data = {};
|
||||
map.set(index, data);
|
||||
}
|
||||
if (!backData) {
|
||||
backData = {};
|
||||
map.set(backIndex, backData);
|
||||
}
|
||||
|
||||
data[v.toDir] = {
|
||||
x: v.fx + toFdx,
|
||||
y: v.fy + toFdy,
|
||||
dir: backDir(v.dir)
|
||||
};
|
||||
backData[backDir(v.toDir)] = {
|
||||
x: v.fx + toTdx,
|
||||
y: v.fy + toTdy,
|
||||
dir: v.dir
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initPortals() {
|
||||
generatePortalMap();
|
||||
}
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
let levels: number[] = [];
|
||||
|
||||
export type Chapter = 'chapter1' | 'chapter2';
|
||||
|
||||
export interface Skill {
|
||||
index: number;
|
||||
title: string;
|
||||
desc: string[];
|
||||
consume: (level: number) => number;
|
||||
front: [skill: number, level: number][];
|
||||
loc: LocArr;
|
||||
max: number;
|
||||
effect: (level: number) => string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Record<Chapter, Skill[]>}
|
||||
*/
|
||||
export const skills: Record<Chapter, Skill[]> = {
|
||||
chapter1: [
|
||||
{
|
||||
index: 0,
|
||||
title: '力量',
|
||||
desc: ['力量就是根本!可以通过智慧增加力量,每级增加2点攻击。'],
|
||||
consume: level => 10 * level + 10,
|
||||
front: [],
|
||||
loc: [1, 2],
|
||||
max: 10,
|
||||
effect: level => [`攻击 + ${level * 2}`]
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
title: '致命一击',
|
||||
desc: ['爆发出全部力量攻击敌人,每级增加5点额外攻击。'],
|
||||
consume: level => 30 * level + 30,
|
||||
front: [[0, 5]],
|
||||
loc: [2, 1],
|
||||
max: 10,
|
||||
effect: level => [`额外攻击 + ${level * 5}`]
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
title: '断灭之刃',
|
||||
desc: [
|
||||
'<span style="color: gold">主动技能,快捷键1</span>,',
|
||||
'开启后会在战斗时会额外增加一定量的攻击,但同时减少一定量的防御。'
|
||||
],
|
||||
consume: level => 200 * level + 400,
|
||||
front: [[1, 5]],
|
||||
loc: [4, 1],
|
||||
max: 5,
|
||||
effect: level => [`增加${level * 10}%攻击,减少${level * 10}%防御`]
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
title: '坚韧',
|
||||
desc: ['由智慧转化出坚韧!每级增加2点防御'],
|
||||
consume: level => 10 * level + 10,
|
||||
front: [],
|
||||
loc: [1, 4],
|
||||
max: 10,
|
||||
effect: level => [`防御 + ${level * 2}`]
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
title: '回春',
|
||||
desc: ['让智慧化为治愈之泉水!每级增加1点生命回复'],
|
||||
consume: level => 20 * level + 20,
|
||||
front: [[3, 5]],
|
||||
loc: [2, 5],
|
||||
max: 25,
|
||||
effect: level => [`生命回复 + ${level}`]
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
title: '治愈之泉',
|
||||
desc: [
|
||||
'让生命变得更多一些吧!每吃50瓶血瓶就增加当前生命回复10%的生命回复'
|
||||
],
|
||||
consume: () => 1500,
|
||||
front: [[4, 25]],
|
||||
loc: [4, 5],
|
||||
max: 1,
|
||||
effect: () => [`50瓶血10%生命回复`]
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
title: '坚固之盾',
|
||||
desc: ['让护甲更加坚硬一些吧!每级增加10点防御'],
|
||||
consume: level => 50 + level * 50,
|
||||
front: [[3, 5]],
|
||||
loc: [2, 3],
|
||||
max: 10,
|
||||
effect: level => [`防御 + ${level * 10}`]
|
||||
},
|
||||
{
|
||||
index: 7,
|
||||
title: '无上之盾',
|
||||
desc: [
|
||||
'<span style="color: #dd4">第一章终极技能</span>,战斗时智慧的 1/10 会充当等量护盾。'
|
||||
],
|
||||
consume: () => 2500,
|
||||
front: [
|
||||
[6, 10],
|
||||
[5, 1],
|
||||
[2, 2]
|
||||
],
|
||||
loc: [5, 3],
|
||||
max: 1,
|
||||
effect: () => [`战斗时智慧会充当护盾`]
|
||||
}
|
||||
],
|
||||
chapter2: [
|
||||
{
|
||||
index: 8,
|
||||
title: '锋利',
|
||||
desc: ['让剑变得更加锋利!每级使攻击增加1%(buff式增加)'],
|
||||
consume: level => (level > 5 ? 50 * level ** 2 : 250 * level + 250),
|
||||
front: [],
|
||||
loc: [1, 2],
|
||||
max: 15,
|
||||
effect: level => [`攻击增加${level}%`]
|
||||
},
|
||||
{
|
||||
index: 9,
|
||||
title: '坚硬',
|
||||
desc: ['让盾牌变得更加坚固!每级使防御增加1%(buff式增加)'],
|
||||
consume: level => (level > 5 ? 50 * level ** 2 : 250 * level + 250),
|
||||
front: [],
|
||||
loc: [1, 4],
|
||||
max: 15,
|
||||
effect: level => [`防御增加${level}%`]
|
||||
},
|
||||
{
|
||||
index: 10,
|
||||
title: '铸剑为盾',
|
||||
desc: [
|
||||
'<span style="color: gold">主动技能,快捷键3</span>,',
|
||||
'减少一定的攻击,增加一定的防御'
|
||||
],
|
||||
consume: level => 1000 * level ** 2 + 1000,
|
||||
front: [[9, 5]],
|
||||
loc: [2, 5],
|
||||
max: 5,
|
||||
effect: level => [
|
||||
`增加${level * 10}%的防御,减少${level * 10}%的攻击`
|
||||
]
|
||||
},
|
||||
{
|
||||
index: 11,
|
||||
title: '魔法盾',
|
||||
desc: ['为主角提供魔法防御,每级增加100点魔法防御'],
|
||||
consume: level => 5000 * level + 5000,
|
||||
front: [
|
||||
[8, 10],
|
||||
[12, 10]
|
||||
],
|
||||
loc: [4, 1],
|
||||
max: 10,
|
||||
effect: level => [`魔法防御 + ${level * 100}`]
|
||||
},
|
||||
{
|
||||
index: 12,
|
||||
title: '聪慧',
|
||||
desc: ['使主角变得更加聪明,每级使绿宝石增加的智慧点上升5%'],
|
||||
consume: level =>
|
||||
level > 5 ? 100 * level ** 2 : 250 * level + 1250,
|
||||
front: [
|
||||
[8, 10],
|
||||
[9, 10]
|
||||
],
|
||||
loc: [3, 3],
|
||||
max: 20,
|
||||
effect: level => [`增加${level * 5}%绿宝石效果`]
|
||||
},
|
||||
{
|
||||
index: 13,
|
||||
title: '治愈',
|
||||
desc: ['使主角能够更好地回复生命,每级使血瓶的加血量增加2%'],
|
||||
consume: level =>
|
||||
level > 5 ? 100 * level ** 2 : 250 * level + 1250,
|
||||
front: [[10, 3]],
|
||||
loc: [4, 5],
|
||||
max: 10,
|
||||
effect: level => [`增加${level * 2}%的血瓶回血量`]
|
||||
},
|
||||
{
|
||||
index: 14,
|
||||
title: '胜利之号',
|
||||
desc: [
|
||||
'<span style="color: #dd4">第二章终极技能</span>,',
|
||||
'勇士攻防增加10%(buff式增加)'
|
||||
],
|
||||
consume: () => 25000,
|
||||
front: [
|
||||
[13, 10],
|
||||
[12, 10],
|
||||
[11, 3]
|
||||
],
|
||||
loc: [5, 3],
|
||||
max: 1,
|
||||
effect: () => [`攻防增加10%`]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export function resetSkillLevel() {
|
||||
levels = [];
|
||||
}
|
||||
|
||||
export function getSkillFromIndex(index: number) {
|
||||
for (const [, skill] of Object.entries(skills)) {
|
||||
const s = skill.find(v => v.index === index);
|
||||
if (s) return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取技能等级
|
||||
*/
|
||||
export function getSkillLevel(skill: number) {
|
||||
levels[skill] ??= 0;
|
||||
return levels[skill];
|
||||
}
|
||||
|
||||
export function getSkillConsume(skill: number) {
|
||||
const s = getSkillFromIndex(skill);
|
||||
if (!s) return 0;
|
||||
return s.consume(getSkillLevel(skill));
|
||||
}
|
||||
|
||||
export function openTree() {
|
||||
if (main.replayChecking) return;
|
||||
Mota.require('@motajs/legacy-ui').mainUi.open('skillTree');
|
||||
}
|
||||
|
||||
/**
|
||||
* 能否升级某个技能
|
||||
*/
|
||||
export function canUpgrade(skill: number) {
|
||||
const consume = getSkillConsume(skill);
|
||||
if (consume > core.status.hero.mdef) return false;
|
||||
const level = getSkillLevel(skill);
|
||||
const s = getSkillFromIndex(skill);
|
||||
if (!s) return false;
|
||||
if (level >= s.max) return false;
|
||||
const front = s.front;
|
||||
for (const [skill, level] of front) {
|
||||
if (getSkillLevel(skill) < level) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际升级效果
|
||||
* @param {number} skill
|
||||
*/
|
||||
export function upgradeSkill(skill: number) {
|
||||
if (!canUpgrade(skill)) return false;
|
||||
switch (skill) {
|
||||
case 0: // 力量 +2攻击
|
||||
core.status.hero.atk += 2;
|
||||
break;
|
||||
case 1: // 致命一击 +5额外攻击
|
||||
core.status.hero.mana += 5;
|
||||
break;
|
||||
case 2: // 断灭之刃
|
||||
HeroSkill.learnSkill(HeroSkill.Blade);
|
||||
break;
|
||||
case 3: // 坚韧 +2防御
|
||||
core.status.hero.def += 2;
|
||||
break;
|
||||
case 4: // 回春 +1回复
|
||||
core.status.hero.hpmax += 1;
|
||||
break;
|
||||
case 5: // 治愈之泉
|
||||
core.setFlag('spring', true);
|
||||
break;
|
||||
case 6: // 坚固之盾 +10防御
|
||||
core.status.hero.def += 10;
|
||||
break;
|
||||
case 7: // 无上之盾
|
||||
core.setFlag('superSheild', true);
|
||||
break;
|
||||
case 8: // 锋利 +1%攻击
|
||||
core.addBuff('atk', 0.01);
|
||||
break;
|
||||
case 9: // 锋利 +1%防御
|
||||
core.addBuff('def', 0.01);
|
||||
break;
|
||||
case 10: // 铸剑为盾
|
||||
HeroSkill.learnSkill(HeroSkill.Shield);
|
||||
break;
|
||||
case 11: // 魔法盾
|
||||
core.status.hero.magicDef += 100;
|
||||
break;
|
||||
case 14:
|
||||
core.addBuff('atk', 0.1);
|
||||
core.addBuff('def', 0.1);
|
||||
break;
|
||||
}
|
||||
const consume = getSkillConsume(skill);
|
||||
core.status.hero.mdef -= consume;
|
||||
levels[skill]++;
|
||||
core.updateStatusBar();
|
||||
return true;
|
||||
}
|
||||
|
||||
export function saveSkillTree() {
|
||||
return levels.slice();
|
||||
}
|
||||
|
||||
export function loadSkillTree(data: number[]) {
|
||||
levels = data ?? [];
|
||||
}
|
||||
|
||||
export namespace HeroSkill {
|
||||
export const enum Skill {
|
||||
None,
|
||||
/** 断灭之刃 */
|
||||
Blade,
|
||||
/** 铸剑为盾 */
|
||||
Shield,
|
||||
/** 跳跃 */
|
||||
Jump
|
||||
}
|
||||
|
||||
export const Blade = Skill.Blade;
|
||||
export const Shield = Skill.Shield;
|
||||
export const Jump = Skill.Jump;
|
||||
|
||||
const skillNameMap = new Map<Skill, string>([
|
||||
[Skill.Blade, '断灭之刃'],
|
||||
[Skill.Shield, '铸剑为盾'],
|
||||
[Skill.Jump, '跳跃']
|
||||
]);
|
||||
|
||||
const skillDesc = new Map<Skill, (level: number) => string>([
|
||||
[
|
||||
Skill.Blade,
|
||||
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
|
||||
],
|
||||
[
|
||||
Skill.Shield,
|
||||
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
|
||||
],
|
||||
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
|
||||
]);
|
||||
|
||||
interface SkillSave {
|
||||
autoSkill: boolean;
|
||||
learned: Skill[];
|
||||
}
|
||||
|
||||
const learned = new Set<Skill>();
|
||||
let autoSkill = true;
|
||||
let enabled: Skill = Skill.None;
|
||||
|
||||
export function getLevel(skill: Skill = getEnabled()) {
|
||||
switch (skill) {
|
||||
case Blade:
|
||||
return getSkillLevel(2);
|
||||
case Jump:
|
||||
return learned.has(Jump) ? 1 : 0;
|
||||
case Shield:
|
||||
return getSkillLevel(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getSkillName(skill: Skill = getEnabled()) {
|
||||
return skillNameMap.get(skill) ?? '未开启技能';
|
||||
}
|
||||
|
||||
export function getSkillDesc(
|
||||
skill: Skill = getEnabled(),
|
||||
level: number = getLevel()
|
||||
) {
|
||||
return skillDesc.get(skill)?.(level) ?? '';
|
||||
}
|
||||
|
||||
export function setAutoSkill(auto: boolean) {
|
||||
autoSkill = auto;
|
||||
}
|
||||
|
||||
export function getAutoSkill() {
|
||||
return autoSkill;
|
||||
}
|
||||
|
||||
export function learnedSkill(skill: Skill) {
|
||||
return learned.has(skill);
|
||||
}
|
||||
|
||||
export function learnSkill(skill: Skill) {
|
||||
learned.add(skill);
|
||||
}
|
||||
|
||||
export function forgetSkill(skill: Skill) {
|
||||
learned.delete(skill);
|
||||
}
|
||||
|
||||
export function clearSkill() {
|
||||
learned.clear();
|
||||
}
|
||||
|
||||
export function saveSkill(): SkillSave {
|
||||
return { autoSkill, learned: [...learned] };
|
||||
}
|
||||
|
||||
export function loadSkill(skills: SkillSave) {
|
||||
learned.clear();
|
||||
for (const skill of skills.learned) {
|
||||
learned.add(skill);
|
||||
}
|
||||
autoSkill = skills.autoSkill;
|
||||
}
|
||||
|
||||
export function getAll() {
|
||||
return learned;
|
||||
}
|
||||
|
||||
export function toggleSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
if (enabled !== skill) enabled = skill;
|
||||
else enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function enableSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
enabled = skill;
|
||||
}
|
||||
|
||||
export function disableSkill() {
|
||||
enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
@@ -59,7 +58,6 @@ function getRealStatus(
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId: FloorIds = core.status.floorId
|
||||
): any {
|
||||
const { getSkillLevel } = Mota.require('@user/data-state');
|
||||
if (name instanceof Array) {
|
||||
const res: any = {};
|
||||
name.forEach(v => {
|
||||
@@ -94,29 +92,6 @@ function getRealStatus(
|
||||
|
||||
if (typeof s !== 'number') return s;
|
||||
|
||||
// 永夜、极昼
|
||||
if (name === 'atk' || name === 'def') {
|
||||
s += NightSpecial.getNight(floorId);
|
||||
}
|
||||
|
||||
const enabled = HeroSkill.getEnabled();
|
||||
// 技能
|
||||
if (enabled === HeroSkill.Blade) {
|
||||
const level = getSkillLevel(2);
|
||||
if (name === 'atk') {
|
||||
s *= 1 + 0.1 * level;
|
||||
} else if (name === 'def') {
|
||||
s *= 1 - 0.1 * level;
|
||||
}
|
||||
} else if (enabled === HeroSkill.Shield) {
|
||||
const level = getSkillLevel(10);
|
||||
if (name === 'atk') {
|
||||
s *= 1 - 0.1 * level;
|
||||
} else if (name === 'def') {
|
||||
s *= 1 + 0.1 * level;
|
||||
}
|
||||
}
|
||||
|
||||
// buff
|
||||
s *= core.status.hero.buff[name] ?? 1;
|
||||
s = Math.floor(s);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export function create() {}
|
||||
|
||||
export * from './hero';
|
||||
export * from './interface';
|
||||
export * from './item';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { backDir, checkCanMoveExtended, toDir } from './utils';
|
||||
import { backDir, toDir } from './utils';
|
||||
import { loading } from '@user/data-base';
|
||||
import type { RenderAdapter } from '@motajs/render';
|
||||
import type {
|
||||
@@ -9,10 +9,8 @@ import type {
|
||||
HeroRenderer,
|
||||
Layer,
|
||||
LayerFloorBinder,
|
||||
LayerGroup,
|
||||
LayerMovingRenderable
|
||||
} from '@user/client-modules';
|
||||
import { BluePalace, MiscData } from '../mechanism/misc';
|
||||
import { sleep } from '@motajs/common';
|
||||
|
||||
// todo: 转身功能
|
||||
@@ -414,26 +412,13 @@ interface CanMoveStatus {
|
||||
noPass: boolean;
|
||||
}
|
||||
|
||||
interface PortalStatus {
|
||||
/** 下一步是否会步入传送门 */
|
||||
portal: boolean;
|
||||
/** 传送门会传到哪 */
|
||||
data?: BluePalace.PortalTo;
|
||||
}
|
||||
|
||||
const enum HeroMoveCode {
|
||||
Step,
|
||||
Stop,
|
||||
/** 不能移动,并撞击前面一格的图块,触发其触发器 */
|
||||
Hit,
|
||||
/** 不能移动,同时当前格有CannotOut,或目标格有CannotIn,不会触发前面一格的触发器 */
|
||||
CannotMove,
|
||||
/** 进入传送门 */
|
||||
Portal,
|
||||
/** 循环式地图 */
|
||||
Loop,
|
||||
/** 循环式地图撞击 */
|
||||
LoopHit
|
||||
CannotMove
|
||||
}
|
||||
|
||||
export class HeroMover extends ObjectMoverBase {
|
||||
@@ -454,9 +439,6 @@ export class HeroMover extends ObjectMoverBase {
|
||||
/** 本次移动开始时的移动速度 */
|
||||
private beforeMoveSpeed: number = 100;
|
||||
|
||||
/** 这一步的传送门信息 */
|
||||
private portalData?: BluePalace.PortalTo;
|
||||
|
||||
override startMove(
|
||||
ignoreTerrain: boolean = false,
|
||||
noRoute: boolean = false,
|
||||
@@ -546,16 +528,6 @@ export class HeroMover extends ObjectMoverBase {
|
||||
this.moveDir = dir4Move;
|
||||
}
|
||||
|
||||
// 检查传送门
|
||||
if (!this.ignoreTerrain) {
|
||||
const { portal, data } = this.checkPortal(x, y, dir4Move);
|
||||
if (portal && data) {
|
||||
this.portalData = data;
|
||||
await this.renderHeroSwap(data);
|
||||
return HeroMoveCode.Portal;
|
||||
}
|
||||
}
|
||||
|
||||
const dir = this.moveDir;
|
||||
if (!this.ignoreTerrain) {
|
||||
const { noPass, canMove } = this.checkCanMove(x, y, dir4Move);
|
||||
@@ -563,25 +535,6 @@ export class HeroMover extends ObjectMoverBase {
|
||||
if (!canMove) {
|
||||
return HeroMoveCode.CannotMove;
|
||||
}
|
||||
// 循环式地图
|
||||
const floorId = core.status.floorId;
|
||||
if (MiscData.loopMaps.has(core.status.floorId)) {
|
||||
const floor = core.status.maps[floorId];
|
||||
const width = floor.width;
|
||||
if (
|
||||
(x === 0 && dir === 'left') ||
|
||||
(x === width - 1 && dir === 'right')
|
||||
) {
|
||||
if (noPass) {
|
||||
return HeroMoveCode.LoopHit;
|
||||
}
|
||||
await Promise.all([
|
||||
this.renderHeroLoop(),
|
||||
this.moveAnimate(nx, ny, showDir, dir)
|
||||
]);
|
||||
return HeroMoveCode.Loop;
|
||||
}
|
||||
}
|
||||
// 不能移动
|
||||
if (noPass) {
|
||||
return HeroMoveCode.Hit;
|
||||
@@ -632,25 +585,9 @@ export class HeroMover extends ObjectMoverBase {
|
||||
}
|
||||
|
||||
// 本次移动正常完成
|
||||
if (
|
||||
code === HeroMoveCode.Step ||
|
||||
code === HeroMoveCode.Portal ||
|
||||
code === HeroMoveCode.Loop
|
||||
) {
|
||||
if (code === HeroMoveCode.Portal) {
|
||||
const data = this.portalData;
|
||||
if (!data) return;
|
||||
core.setHeroLoc('x', data.x);
|
||||
core.setHeroLoc('y', data.y);
|
||||
core.setHeroLoc('direction', data.dir);
|
||||
} else if (code === HeroMoveCode.Loop) {
|
||||
const map = core.status.thisMap;
|
||||
if (x === 0) core.setHeroLoc('x', map.width - 1);
|
||||
else core.setHeroLoc('x', 0);
|
||||
} else {
|
||||
core.setHeroLoc('x', nx, true);
|
||||
core.setHeroLoc('y', ny, true);
|
||||
}
|
||||
if (code === HeroMoveCode.Step) {
|
||||
core.setHeroLoc('x', nx, true);
|
||||
core.setHeroLoc('y', ny, true);
|
||||
|
||||
if (!this.ignoreTerrain) {
|
||||
const direction = core.getHeroLoc('direction');
|
||||
@@ -713,7 +650,7 @@ export class HeroMover extends ObjectMoverBase {
|
||||
core.status.automaticRoute.moveStepBeforeStop = [];
|
||||
core.status.automaticRoute.lastDirection = dir;
|
||||
|
||||
if (core.status.automaticRoute.moveStepBeforeStop.length == 0) {
|
||||
if (core.status.automaticRoute.moveStepBeforeStop.length === 0) {
|
||||
core.clearContinueAutomaticRoute();
|
||||
core.stopAutomaticRoute();
|
||||
}
|
||||
@@ -727,20 +664,6 @@ export class HeroMover extends ObjectMoverBase {
|
||||
*/
|
||||
private checkCanMove(x: number, y: number, dir: Dir): CanMoveStatus {
|
||||
// 如果是循环式地图
|
||||
const floorId = core.status.floorId;
|
||||
if (MiscData.loopMaps.has(floorId)) {
|
||||
const floor = core.status.maps[floorId];
|
||||
const width = floor.width;
|
||||
if (x === 0 && dir === 'left') {
|
||||
const noPass = core.noPass(width - 1, y);
|
||||
const move = checkCanMoveExtended(0, y, width - 1, y, 'left');
|
||||
return { noPass, canMove: move };
|
||||
} else if (x === width - 1 && dir === 'right') {
|
||||
const noPass = core.noPass(0, y);
|
||||
const move = checkCanMoveExtended(width - 1, y, 0, y, 'right');
|
||||
return { noPass, canMove: move };
|
||||
}
|
||||
}
|
||||
const { x: nx, y: ny } = this.nextLoc(x, y, dir);
|
||||
const noPass = core.noPass(nx, ny);
|
||||
const canMove = core.canMoveHero(x, y, dir);
|
||||
@@ -759,173 +682,6 @@ export class HeroMover extends ObjectMoverBase {
|
||||
const ny = y + dy;
|
||||
return { x: nx, y: ny };
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查前方一格是否会步入传送门
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
* @param dir 移动方向
|
||||
*/
|
||||
private checkPortal(x: number, y: number, dir: Dir): PortalStatus {
|
||||
const map = BluePalace.portalMap.get(core.status.floorId);
|
||||
if (!map) {
|
||||
return { portal: false };
|
||||
}
|
||||
const width = core.status.thisMap.width;
|
||||
const index = x + y * width;
|
||||
const data = map?.get(index);
|
||||
if (!data) {
|
||||
return { portal: false };
|
||||
}
|
||||
const to = data[dir];
|
||||
if (to) {
|
||||
return { portal: true, data: to };
|
||||
}
|
||||
return { portal: false };
|
||||
}
|
||||
|
||||
private renderHeroSwap(data: BluePalace.PortalTo) {
|
||||
const adapter = HeroMover.adapter;
|
||||
if (!adapter) return;
|
||||
const list = adapter.items;
|
||||
const { x: tx, y: ty, dir: toDir } = data;
|
||||
const { x, y, direction } = core.status.hero.loc;
|
||||
const { x: dx } = core.utils.scan[direction];
|
||||
const { x: tdx } = core.utils.scan[toDir];
|
||||
|
||||
const promises = [...list].map(v => {
|
||||
if (!v.renderable) return;
|
||||
const renderable = { ...v.renderable };
|
||||
renderable.render = v.getRenderFromDir(toDir);
|
||||
renderable.zIndex = ty;
|
||||
const heroDir = v.moveDir;
|
||||
|
||||
const width = v.renderable.render[0][2];
|
||||
const height = v.renderable.render[0][3];
|
||||
const cell = v.layer.cellSize;
|
||||
const restHeight = height - cell;
|
||||
if (!width || !height) return;
|
||||
|
||||
const originFrom = structuredClone(v.renderable.render);
|
||||
const originTo = structuredClone(renderable.render);
|
||||
v.layer.moving.add(renderable);
|
||||
v.layer.requestUpdateMoving();
|
||||
|
||||
const start = Date.now();
|
||||
return new Promise<void>(res => {
|
||||
const tick = () => {
|
||||
const now = Date.now();
|
||||
const progress = (now - start) / this.moveSpeed;
|
||||
const clipWidth = cell * progress;
|
||||
const clipHeight = cell * progress;
|
||||
const beforeWidth = width - clipWidth;
|
||||
const beforeHeight = height - clipHeight;
|
||||
|
||||
v.renderable!.x = x;
|
||||
v.renderable!.y = y;
|
||||
if (heroDir === 'left' || heroDir === 'right') {
|
||||
v.renderable!.x = x + (clipWidth / 2 / cell) * dx;
|
||||
v.renderable!.render.forEach((v, i) => {
|
||||
v[2] = beforeWidth;
|
||||
if (heroDir === 'left') {
|
||||
v[0] = originFrom[i][0] + clipWidth;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
v.renderable!.render.forEach((v, i) => {
|
||||
v[3] = beforeHeight;
|
||||
if (heroDir === 'up') {
|
||||
v[1] =
|
||||
originFrom[i][1] + clipHeight + restHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderable.x = tx;
|
||||
renderable.y = ty;
|
||||
if (toDir === 'left' || toDir === 'right') {
|
||||
renderable.x = tx + (clipWidth / 2 / cell - 0.5) * tdx;
|
||||
renderable.render.forEach((v, i) => {
|
||||
v[2] = clipWidth;
|
||||
if (toDir === 'right') {
|
||||
v[0] = originTo[i][0] + beforeWidth;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (toDir === 'down') renderable.y = ty - 1 + progress;
|
||||
renderable.render.forEach((v, i) => {
|
||||
v[3] = clipHeight + restHeight;
|
||||
if (toDir === 'down') {
|
||||
v[1] = originTo[i][1] + clipHeight + restHeight;
|
||||
v[3] = clipHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
v.layer.delegateTicker(tick, this.moveSpeed, () => {
|
||||
v.renderable!.render = originFrom;
|
||||
v.setAnimateDir(data.dir);
|
||||
v.layer.moving.delete(renderable);
|
||||
v.layer.requestUpdateMoving();
|
||||
res();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private renderHeroLoop() {
|
||||
const adapter = HeroMover.adapter;
|
||||
const viewport = HeroMover.viewport;
|
||||
if (!adapter || !viewport) return;
|
||||
const MotaRenderer = Mota.require('@motajs/render').MotaRenderer;
|
||||
const render = MotaRenderer.get('render-main');
|
||||
const group = render?.getElementById('layer-loop') as LayerGroup;
|
||||
const layer = group?.getLayer('event');
|
||||
const mainGroup = render?.getElementById('layer-main') as LayerGroup;
|
||||
const mainLayer = mainGroup?.getLayer('event');
|
||||
const hero = mainLayer?.getExtends('floor-hero') as HeroRenderer;
|
||||
const renderable = hero?.renderable;
|
||||
if (!layer || !hero || !renderable) return;
|
||||
const { x, y } = core.status.hero.loc;
|
||||
const width = core.status.thisMap.width;
|
||||
const loopHero = { ...renderable };
|
||||
layer.moving.add(loopHero);
|
||||
|
||||
let target: number;
|
||||
let from: number;
|
||||
if (x === 0) {
|
||||
from = width;
|
||||
target = width - 1;
|
||||
} else {
|
||||
from = -1;
|
||||
target = 0;
|
||||
}
|
||||
const delta = target - from;
|
||||
loopHero.x = from;
|
||||
|
||||
layer.requestUpdateMoving();
|
||||
|
||||
const startTime = Date.now();
|
||||
return new Promise<void>(res => {
|
||||
layer.delegateTicker(
|
||||
() => {
|
||||
const progress = (Date.now() - startTime) / this.moveSpeed;
|
||||
const dx = delta * progress;
|
||||
loopHero.x = dx + from;
|
||||
layer.update(layer);
|
||||
},
|
||||
this.moveSpeed,
|
||||
() => {
|
||||
layer.moving.delete(loopHero);
|
||||
layer.requestUpdateMoving();
|
||||
viewport.all('setPosition', x === 0 ? width - 1 : 0, y);
|
||||
res();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface HeroMoveCollection {
|
||||
|
||||
Reference in new issue
Block a user