ginka-generator/data/src/auto/tower.ts
2025-12-11 22:56:44 +08:00

441 lines
15 KiB
TypeScript

import { ICodeRunResult, IConvertedMap, INeededFloorData } from './types';
/**
* 运行塔的代码
* @param project project.min.js 内容
* @param floors floors.min.js 内容
*/
export function runTowerCode(project: string, floors: string): ICodeRunResult {
const result: Partial<ICodeRunResult> = {
issue: [],
main: {
floors: {}
}
};
const projectCode =
project +
`
result.data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
result.enemy = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;
result.map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
result.item = items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a;
`;
const main = result.main!;
try {
eval(projectCode);
eval(floors);
if (Object.keys(main.floors).length === 0) {
result.issue?.push(`楼层信息为空`);
}
} catch {
result.issue?.push(`代码运行错误`);
}
return result as ICodeRunResult;
}
function edge(x: number, y: number, width: number, height: number) {
return x === 0 || y === 0 || x === width - 1 || y === height - 1;
}
export function convertTowerMap(
result: ICodeRunResult,
floor: INeededFloorData
): IConvertedMap {
const width = floor.map[0].length;
const height = floor.map.length;
let hasCannotInOut = false;
const converted: number[][] = Array.from({ length: height }, () =>
Array.from<number>({ length: width }).fill(0)
);
/** 键表示怪物位置,值表示怪物的生命乘以攻加防 */
const enemyMap = new Map<number, number>();
/** 键表示道具位置,值表示道具增加的血、攻、防、盾属性 */
const itemMap = new Map<number, [number, number, number, number]>();
// 这些是为了区分宝石,一个地图只有两种宝石的话当然没必要把三种宝石都用上
const itemHpSet = new Set<number>();
const itemAtkSet = new Set<number>();
const itemDefSet = new Set<number>();
const itemMdefSet = new Set<number>();
const heroStatus: Record<string, number> = {
hp: 0,
atk: 0,
def: 0,
mdef: 0
};
const thisMap = {
ratio: 1
};
// 给后面的 eval 用的
const core = {
values: new Proxy(result.data.values, {
set() {
// 防止被修改
return true;
}
}),
status: {
hero: new Proxy(heroStatus, {
set(target, p: string, newValue) {
if (typeof newValue !== 'number') return true;
if (
p !== 'hp' &&
p !== 'atk' &&
p !== 'def' &&
p !== 'mdef'
) {
return true;
}
target[p] = newValue;
return true;
}
}),
thisMap: new Proxy(thisMap, {
set() {
// 防止被修改
return true;
}
})
}
};
core.status.hero.hp = 0;
core.status.hero.atk = 0;
core.status.hero.def = 0;
core.status.hero.mdef = 0;
for (let nx = 0; nx < width; nx++) {
for (let ny = 0; ny < height; ny++) {
const num = floor.map[ny][nx];
if (num === 0) {
converted[ny][nx] = 0;
continue;
} else if (num === 17) {
hasCannotInOut = true;
converted[ny][nx] = 0;
continue;
}
const loc = `${nx},${ny}`;
if (floor.changeFloor[loc]) {
converted[ny][nx] = 29;
continue;
}
const block = result.map[num];
if (!block) {
// 图块不存在说明是额外素材中的内容,默认不可通行,视为墙壁
converted[ny][nx] = 1;
continue;
}
// 怪物处理
if (block.cls === 'enemys' || block.cls === 'enemy48') {
const enemy = result.enemy[block.id];
if (!enemy) {
converted[ny][nx] = 0;
continue;
}
const value = enemy.hp * (enemy.atk + enemy.def);
enemyMap.set(ny * width + nx, value);
continue;
}
// 道具处理
if (block.cls === 'items') {
const item = result.item[block.id];
if (!item) {
converted[ny][nx] = 0;
continue;
}
// 先清空内容
heroStatus.hp = 0;
heroStatus.atk = 0;
heroStatus.def = 0;
heroStatus.mdef = 0;
if (block.id === 'pickaxe') {
converted[ny][nx] = 24;
continue;
} else if (block.id === 'bomb') {
converted[ny][nx] = 24;
continue;
} else if (block.id === 'centerFly') {
converted[ny][nx] = 23;
continue;
} else if (block.id === 'yellowKey') {
converted[ny][nx] = 7;
continue;
} else if (block.id === 'blueKey') {
converted[ny][nx] = 8;
continue;
} else if (block.id === 'redKey') {
converted[ny][nx] = 9;
continue;
}
// 执行道具效果
if (item.cls === 'items' && item.itemEffect) {
try {
eval(item.itemEffect);
} catch {
// 执行失败就清空一下防止被误识别为宝石血瓶
heroStatus.hp = 0;
heroStatus.atk = 0;
heroStatus.def = 0;
heroStatus.mdef = 0;
}
}
const arr: [number, number, number, number] = [
heroStatus.hp,
heroStatus.atk,
heroStatus.def,
heroStatus.mdef
];
let isResouce = false;
// 对每个属性进行判断
if (heroStatus.hp > 0) {
isResouce = true;
itemHpSet.add(heroStatus.hp);
}
if (heroStatus.atk > 0) {
isResouce = true;
itemAtkSet.add(heroStatus.atk);
}
if (heroStatus.def > 0) {
isResouce = true;
itemDefSet.add(heroStatus.def);
}
if (heroStatus.mdef > 0) {
isResouce = true;
itemMdefSet.add(heroStatus.mdef);
}
if (isResouce) {
itemMap.set(ny * width + nx, arr);
continue;
} else {
converted[ny][nx] = 0;
continue;
}
}
// 门信息,这种处理方式只能处理 2.7+ 的塔,老塔估计处理不了,不过老塔占比也不大,忽略就好了
if (block.doorInfo && Object.keys(block.doorInfo.keys).length > 0) {
if (block.id === 'specialDoor') {
converted[ny][nx] = 6;
continue;
} else if (
'redKey' in block.doorInfo.keys ||
'greenKey' in block.doorInfo.keys
) {
converted[ny][nx] = 5;
continue;
} else if ('blueKey' in block.doorInfo.keys) {
converted[ny][nx] = 4;
continue;
} else if ('yellowKey' in block.doorInfo.keys) {
converted[ny][nx] = 3;
continue;
} else {
// 其他门一律视为黄门
converted[ny][nx] = 3;
continue;
}
}
// 不可入和不可出现在还没办法处理,这两个还需要同时考虑背景前景
const bgNum = floor.bgmap?.[ny]?.[nx] ?? 0;
const bg2Num = floor.bg2map?.[ny]?.[nx] ?? 0;
const fgNum = floor.fgmap?.[ny]?.[nx] ?? 0;
const fg2Num = floor.fg2map?.[ny]?.[nx] ?? 0;
const bgBlock = result.map[bgNum];
const bg2Block = result.map[bg2Num];
const fgBlock = result.map[fgNum];
const fg2Block = result.map[fg2Num];
if (
block.cannotIn ||
block.cannotOut ||
bgBlock?.cannotIn ||
bgBlock?.cannotOut ||
bg2Block?.cannotIn ||
bg2Block?.cannotOut ||
fgBlock?.cannotIn ||
fgBlock?.cannotOut ||
fg2Block?.cannotIn ||
fg2Block?.cannotOut
) {
converted[ny][nx] = 0;
hasCannotInOut = true;
continue;
}
// 墙壁处理
if (block.canPass) {
converted[ny][nx] = 0;
continue;
} else {
converted[ny][nx] = 1;
}
}
}
// 处理怪物
const minEnemyValue = Math.min(...enemyMap.values());
const maxEnemyValue = Math.max(...enemyMap.values());
const enemyValueDelta = maxEnemyValue - minEnemyValue;
if (enemyValueDelta <= 0) {
// 如果怪物战斗力都一样的话...
enemyMap.forEach((value, pos) => {
const nx = pos % width;
const ny = Math.floor(pos / width);
converted[ny][nx] = 26;
});
} else {
enemyMap.forEach((value, pos) => {
const nx = pos % width;
const ny = Math.floor(pos / width);
const ratio = (value - minEnemyValue) / enemyValueDelta;
const n = Math.min(Math.floor(ratio * 3), 2);
converted[ny][nx] = 26 + n;
});
}
// 处理宝石血瓶
const minHpValue = Math.min(...itemHpSet);
const maxHpValue = Math.max(...itemHpSet);
const minAtkValue = Math.min(...itemAtkSet);
const maxAtkValue = Math.max(...itemAtkSet);
const minDefValue = Math.min(...itemDefSet);
const maxDefValue = Math.max(...itemDefSet);
const minMdefValue = Math.min(...itemMdefSet);
const maxMdefValue = Math.max(...itemMdefSet);
const hpValueDelta = maxHpValue - minHpValue;
const atkValueDelta = maxAtkValue - minAtkValue;
const defValueDelta = maxDefValue - minDefValue;
const mdefValueDelta = maxMdefValue - minMdefValue;
itemMap.forEach(([hp, atk, def, mdef], pos) => {
const nx = pos % width;
const ny = Math.floor(pos / width);
// 资源判定为占比最大的那个
// 如果只有一种资源且道具包含这种属性,全部使用最低的资源种类
if (minHpValue === maxHpValue && hp > 0) {
converted[ny][nx] = 19;
return;
}
if (minAtkValue === maxAtkValue && atk > 0) {
converted[ny][nx] = 10;
return;
}
if (minDefValue === maxDefValue && def > 0) {
converted[ny][nx] = 13;
return;
}
if (minMdefValue === maxMdefValue && mdef > 0) {
converted[ny][nx] = 16;
return;
}
const hpRatio = (hp - minHpValue) / hpValueDelta;
const atkRatio = (atk - minAtkValue) / atkValueDelta;
const defRatio = (def - minDefValue) / defValueDelta;
const mdefRatio = (mdef - minMdefValue) / mdefValueDelta;
// 判断资源种类
const arr = [hpRatio, atkRatio, defRatio, mdefRatio];
let maxIndex = 0;
let maxRatio = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > maxRatio) {
maxRatio = arr[i];
maxIndex = i;
}
}
// 转换图块,对于宝石来说,一共有三个级别的宝石,如果数值只有两种,那么需要额外判断下使用哪两个级别的宝石
// 倍数差距大于 3 的就使用一级和三级宝石,小于等于 3 的就使用一级和二级宝石
// 血瓶不做这个处理
switch (maxIndex) {
case 0: {
// 血瓶
const n = Math.min(Math.floor(hpRatio * 4), 3);
converted[ny][nx] = 19 + n;
break;
}
case 1: {
// 红宝石,这里不可能只有一种数值了,不需要判断
if (itemAtkSet.size === 2) {
if (atkRatio <= 0.5) {
// 小宝石
converted[ny][nx] = 10;
} else {
// 大宝石
if (maxAtkValue / minAtkValue > 3) {
converted[nx][nx] = 12;
} else {
converted[ny][nx] = 11;
}
}
} else {
const n = Math.min(Math.floor(atkRatio * 3), 2);
converted[ny][nx] = 10 + n;
}
break;
}
case 2: {
// 蓝宝石,这里不可能只有一种数值了,不需要判断
if (itemDefSet.size === 2) {
if (defRatio <= 0.5) {
// 小宝石
converted[ny][nx] = 13;
} else {
// 大宝石
if (maxDefValue / minDefValue > 3) {
converted[nx][nx] = 15;
} else {
converted[ny][nx] = 14;
}
}
} else {
const n = Math.min(Math.floor(defRatio * 3), 2);
converted[ny][nx] = 13 + n;
}
break;
}
case 2: {
// 绿宝石,这里不可能只有一种数值了,不需要判断
if (itemMdefSet.size === 2) {
if (mdefRatio <= 0.5) {
// 小宝石
converted[ny][nx] = 16;
} else {
// 大宝石
if (maxMdefValue / minMdefValue > 3) {
converted[nx][nx] = 18;
} else {
converted[ny][nx] = 17;
}
}
} else {
const n = Math.min(Math.floor(mdefRatio * 3), 2);
converted[ny][nx] = 16 + n;
}
break;
}
}
});
for (let nx = 0; nx < width; nx++) {
for (let ny = 0; ny < height; ny++) {
if (
typeof converted[ny][nx] !== 'number' ||
isNaN(converted[ny][nx]) ||
!isFinite(converted[ny][nx]) ||
converted[ny][nx] < 0
) {
converted[ny][nx] = 0;
}
}
}
return {
map: converted,
hasCannotInOut
};
}