526 lines
18 KiB
JavaScript
526 lines
18 KiB
JavaScript
/**
|
||
* LevelData.js
|
||
* Predefined level map configurations.
|
||
* Each level is a 13×21 grid (rows × cols). Values correspond to TERRAIN enum:
|
||
* 0=EMPTY, 1=BRICK, 2=STEEL, 3=RIVER, 4=FOREST, 5=BASE, 6=BASE_WALL
|
||
*
|
||
* In landscape mode the map is 13 rows × 21 cols.
|
||
* The original 13×13 design sits in the center (cols 4–16),
|
||
* with additional terrain on the flanks (cols 0–3 and cols 17–20).
|
||
*
|
||
* Player spawns at bottom area, enemies spawn from top row.
|
||
* Base (5) is at bottom-center, surrounded by BASE_WALL (6).
|
||
*/
|
||
|
||
const LEVELS = [
|
||
// ============================================================
|
||
// Level 1 - Tutorial: open terrain, few bricks
|
||
// ============================================================
|
||
{
|
||
id: 1,
|
||
name: 'Tutorial I',
|
||
grid: [
|
||
//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,0,1,0,0,1,0,1,0,0,1,0,0,0,1,0,0],
|
||
[0,0,0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,0,0,0,0],
|
||
[0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0],
|
||
[0,0,0,0,0,0,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0],
|
||
[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
|
||
[0,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,0],
|
||
[0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
|
||
[0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0],
|
||
[0,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,0],
|
||
[0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 18, fast: 2, armor: 0, boss: 0 },
|
||
},
|
||
speedMultiplier: 0.6,
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 2 - Tutorial II: more bricks, simple layout
|
||
// ============================================================
|
||
{
|
||
id: 2,
|
||
name: 'Tutorial II',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,0,1,1,0,1,1,0,1,1,0,1,1,0,0,0,1,0],
|
||
[0,0,0,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,1,0,0,1,0,1,0,1,0,1,0,0,1,0,0,1,0],
|
||
[0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0],
|
||
[0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0],
|
||
[0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0],
|
||
[0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0],
|
||
[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 16, fast: 4, armor: 0, boss: 0 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 3 - Tutorial III: denser bricks, introduce forest
|
||
// ============================================================
|
||
{
|
||
id: 3,
|
||
name: 'Tutorial III',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,4,0,0,0,1,0,4,0,1,0,1,0,4,0,1,0,0,0,4,0],
|
||
[0,4,0,1,0,1,0,4,0,1,0,1,0,4,0,1,0,1,0,4,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,1,0,1,0,4,0,1,0,1,1,0,0,1,0,0],
|
||
[0,0,0,0,0,0,0,0,1,0,4,0,1,0,0,0,0,0,0,0,0],
|
||
[0,1,0,1,1,0,1,0,0,0,0,0,0,0,1,0,1,1,0,1,0],
|
||
[0,0,0,0,1,0,1,0,0,1,0,1,0,0,1,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0],
|
||
[0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 14, fast: 6, armor: 0, boss: 0 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 4 - Steel Fortress: steel walls appear
|
||
// ============================================================
|
||
{
|
||
id: 4,
|
||
name: 'Steel Fortress',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,2,0,0,1,2,0,1,1,0,1,1,0,2,1,0,0,2,0,0],
|
||
[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0],
|
||
[0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0],
|
||
[0,0,0,0,2,0,0,1,0,2,0,2,0,1,0,0,2,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,2,0,0,0,1,1,0,2,1,0,1,2,0,1,1,0,0,0,2,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,0],
|
||
[0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0],
|
||
[0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 12, fast: 5, armor: 3, boss: 0 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 5 - River Crossing: introduces water terrain
|
||
// ============================================================
|
||
{
|
||
id: 5,
|
||
name: 'River Crossing',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,1,0,0],
|
||
[0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0],
|
||
[0,1,0,0,0,0,0,3,3,0,1,0,3,3,0,0,0,0,0,1,0],
|
||
[0,0,0,1,0,1,0,3,3,0,0,0,3,3,0,1,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,1,0,1,0,3,1,0,1,3,0,1,0,1,0,0,1,0],
|
||
[0,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,0,0],
|
||
[0,1,0,0,0,1,0,1,0,1,1,1,0,1,0,1,0,0,0,1,0],
|
||
[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 10, fast: 6, armor: 4, boss: 0 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 10 - Iron Wall: lots of armored enemies
|
||
// ============================================================
|
||
{
|
||
id: 10,
|
||
name: 'Iron Wall',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,2,0,0,0,2,1,0,2,1,0,1,2,0,1,2,0,0,0,2,0],
|
||
[0,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,1,2,0,1,3,3,0,3,3,1,0,2,1,0,0,1,0],
|
||
[0,0,0,0,0,0,0,1,3,3,0,3,3,1,0,0,0,0,0,0,0],
|
||
[0,0,2,0,0,1,0,0,0,2,0,2,0,0,0,1,0,0,2,0,0],
|
||
[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],
|
||
[0,1,0,1,0,0,0,2,0,1,4,1,0,2,0,0,0,1,0,1,0],
|
||
[0,0,0,0,0,1,0,0,0,1,4,1,0,0,0,1,0,0,0,0,0],
|
||
[0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 6, fast: 4, armor: 10, boss: 0 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
|
||
// ============================================================
|
||
// Level 20 - Boss Battle: giant tank encounter
|
||
// ============================================================
|
||
{
|
||
id: 20,
|
||
name: 'Boss Battle',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,2,0,1,2,0,2,1,0,2,1,0,0,1,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,2,0,0,2,0,0,2,3,0,0,0,3,2,0,0,2,0,0,2,0],
|
||
[0,0,0,0,0,0,0,0,3,0,4,0,3,0,0,0,0,0,0,0,0],
|
||
[0,0,1,0,0,1,2,0,0,0,4,0,0,0,2,1,0,0,1,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,1,0,0,0,2,0,1,0,2,0,2,0,1,0,2,0,0,0,1,0],
|
||
[0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,6,6,6,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,6,6,5,6,6,0,0,0,0,0,0,0,0],
|
||
],
|
||
enemies: {
|
||
total: 20,
|
||
composition: { normal: 8, fast: 4, armor: 6, boss: 2 },
|
||
},
|
||
spawnPoints: [
|
||
{ col: 0, row: 0 },
|
||
{ col: 10, row: 0 },
|
||
{ col: 20, row: 0 },
|
||
],
|
||
playerSpawn: { col: 8, row: 10 },
|
||
},
|
||
];
|
||
|
||
/**
|
||
* Get level data by level number.
|
||
* If the level doesn't exist, generate one based on the closest template
|
||
* with increased difficulty.
|
||
* @param {number} levelNum
|
||
* @returns {object} Level data
|
||
*/
|
||
function getLevelData(levelNum) {
|
||
// Direct match
|
||
const exact = LEVELS.find((l) => l.id === levelNum);
|
||
if (exact) return JSON.parse(JSON.stringify(exact));
|
||
|
||
// Find the closest template (highest id <= levelNum)
|
||
let template = LEVELS[0];
|
||
for (const l of LEVELS) {
|
||
if (l.id <= levelNum && l.id > template.id) {
|
||
template = l;
|
||
}
|
||
}
|
||
|
||
// Clone and adjust difficulty
|
||
const data = JSON.parse(JSON.stringify(template));
|
||
data.id = levelNum;
|
||
data.name = `Level ${levelNum}`;
|
||
|
||
// Scale enemy composition based on level
|
||
const cycle = Math.floor((levelNum - 1) / 20); // difficulty cycle
|
||
const extra = cycle * 2;
|
||
data.enemies.composition.armor = Math.min(
|
||
data.enemies.total,
|
||
data.enemies.composition.armor + extra
|
||
);
|
||
data.enemies.composition.fast = Math.min(
|
||
data.enemies.total - data.enemies.composition.armor,
|
||
data.enemies.composition.fast + cycle
|
||
);
|
||
data.enemies.composition.normal = Math.max(
|
||
0,
|
||
data.enemies.total -
|
||
data.enemies.composition.armor -
|
||
data.enemies.composition.fast -
|
||
data.enemies.composition.boss
|
||
);
|
||
|
||
return data;
|
||
}
|
||
|
||
// ============================================================
|
||
// PVP Maps - Symmetric layouts with bases for 1v1 base-destruction mode
|
||
// Each player has a base (TERRAIN.BASE=5) surrounded by BASE_WALL (6)
|
||
// Player 1 base on left, Player 2 base on right
|
||
// ============================================================
|
||
const PVP_MAPS = [
|
||
{
|
||
id: 1,
|
||
name: 'Arena I',
|
||
grid: [
|
||
//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0],
|
||
[0,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,0],
|
||
[0,6,5,6,1,0,1,0,2,0,0,0,2,0,1,0,1,6,5,6,0],
|
||
[0,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,0],
|
||
[0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
teamASpawns: [{ col: 1, row: 5 }],
|
||
teamBSpawns: [{ col: 19, row: 7 }],
|
||
},
|
||
{
|
||
id: 2,
|
||
name: 'Arena II',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,2,1,0,0,0,1,2,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,1,0,0,4,0,0,1,0,0,0,0,0,0,0],
|
||
[0,6,6,0,0,1,0,0,0,1,4,1,0,0,0,1,0,0,6,6,0],
|
||
[0,6,5,6,0,0,0,0,4,4,0,4,4,0,0,0,0,6,5,6,0],
|
||
[0,6,6,0,0,1,0,0,0,1,4,1,0,0,0,1,0,0,6,6,0],
|
||
[0,0,0,0,0,0,0,1,0,0,4,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,2,1,0,0,0,1,2,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
teamASpawns: [{ col: 0, row: 6 }],
|
||
teamBSpawns: [{ col: 20, row: 6 }],
|
||
},
|
||
{
|
||
id: 3,
|
||
name: 'Arena III',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,1,0,0,2,0,0,0,0,0,2,0,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,3,0,0,0,3,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,0],
|
||
[0,6,6,1,0,2,0,0,0,0,0,0,0,0,0,2,0,1,6,6,0],
|
||
[0,6,5,6,0,0,0,1,0,4,4,4,0,1,0,0,0,6,5,6,0],
|
||
[0,6,6,1,0,2,0,0,0,0,0,0,0,0,0,2,0,1,6,6,0],
|
||
[0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,3,0,0,0,3,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,1,0,0,2,0,0,0,0,0,2,0,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
teamASpawns: [{ col: 1, row: 5 }],
|
||
teamBSpawns: [{ col: 19, row: 7 }],
|
||
},
|
||
];
|
||
|
||
/**
|
||
* Get a PVP map by id, or deterministically by roomId seed.
|
||
* When no mapId is given, uses roomId to pick the same map on both clients.
|
||
* Falls back to random only when neither mapId nor roomId is provided.
|
||
* @param {number} [mapId] - Explicit map id
|
||
* @param {string} [roomId] - Room id used as seed for deterministic selection
|
||
* @returns {object} PVP map data (deep clone).
|
||
*/
|
||
function getPvpMap(mapId, roomId) {
|
||
let map;
|
||
if (mapId) {
|
||
map = PVP_MAPS.find((m) => m.id === mapId);
|
||
}
|
||
if (!map && roomId) {
|
||
// Deterministic selection based on roomId so both clients pick the same map
|
||
let hash = 0;
|
||
for (let i = 0; i < roomId.length; i++) {
|
||
hash = ((hash << 5) - hash + roomId.charCodeAt(i)) | 0;
|
||
}
|
||
const index = Math.abs(hash) % PVP_MAPS.length;
|
||
map = PVP_MAPS[index];
|
||
}
|
||
if (!map) {
|
||
map = PVP_MAPS[Math.floor(Math.random() * PVP_MAPS.length)];
|
||
}
|
||
return JSON.parse(JSON.stringify(map));
|
||
}
|
||
|
||
// ============================================================
|
||
// 3v3 Team Maps - Symmetric layouts with bases on both ends
|
||
// Each team has a base (TERRAIN.BASE=5) surrounded by BASE_WALL (6)
|
||
// Left team base at left end, right team base at right end
|
||
// Center area is the contested zone
|
||
// ============================================================
|
||
const TEAM_MAPS = [
|
||
{
|
||
id: 1,
|
||
name: 'Battlefield I',
|
||
grid: [
|
||
//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,0,0,1,0,0,1,0,2,0,2,0,1,0,0,1,0,0,0,0],
|
||
[0,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,0],
|
||
[0,6,5,6,0,2,0,0,3,3,0,3,3,0,0,2,0,6,5,6,0],
|
||
[0,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,0],
|
||
[0,0,0,0,1,0,0,1,0,2,0,2,0,1,0,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
// Each team has exactly 1 base, centered vertically on their side
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
// Spawn points for Team A (left side, near base)
|
||
teamASpawns: [
|
||
{ col: 1, row: 5 },
|
||
{ col: 0, row: 6 },
|
||
{ col: 1, row: 7 },
|
||
],
|
||
// Spawn points for Team B (right side, near base)
|
||
teamBSpawns: [
|
||
{ col: 19, row: 5 },
|
||
{ col: 20, row: 6 },
|
||
{ col: 19, row: 7 },
|
||
],
|
||
},
|
||
{
|
||
id: 2,
|
||
name: 'Battlefield II',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,0,4,0,4,0,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,1,4,0,4,1,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,2,0,1,0,1,0,2,0,0,0,0,0,0,0],
|
||
[0,6,6,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,6,6,0],
|
||
[0,6,5,6,1,0,2,0,0,0,3,0,0,0,2,0,1,6,5,6,0],
|
||
[0,6,6,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,6,6,0],
|
||
[0,0,0,0,0,0,0,2,0,1,0,1,0,2,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,1,4,0,4,1,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,0,4,0,4,0,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
teamASpawns: [
|
||
{ col: 1, row: 5 },
|
||
{ col: 0, row: 6 },
|
||
{ col: 1, row: 7 },
|
||
],
|
||
teamBSpawns: [
|
||
{ col: 19, row: 5 },
|
||
{ col: 20, row: 6 },
|
||
{ col: 19, row: 7 },
|
||
],
|
||
},
|
||
{
|
||
id: 3,
|
||
name: 'Battlefield III',
|
||
grid: [
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,1,0,0,2,0,0,0,0,0,2,0,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,3,0,0,0,3,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,0],
|
||
[0,6,6,1,0,2,0,0,0,0,0,0,0,0,0,2,0,1,6,6,0],
|
||
[0,6,5,6,0,0,0,1,0,4,4,4,0,1,0,0,0,6,5,6,0],
|
||
[0,6,6,1,0,2,0,0,0,0,0,0,0,0,0,2,0,1,6,6,0],
|
||
[0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,1,0,3,0,0,0,3,0,1,0,0,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0],
|
||
[0,0,0,0,1,0,0,2,0,0,0,0,0,2,0,0,1,0,0,0,0],
|
||
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||
],
|
||
teamABase: [{ col: 2, row: 6 }],
|
||
teamBBase: [{ col: 18, row: 6 }],
|
||
teamASpawns: [
|
||
{ col: 1, row: 5 },
|
||
{ col: 0, row: 6 },
|
||
{ col: 1, row: 7 },
|
||
],
|
||
teamBSpawns: [
|
||
{ col: 19, row: 5 },
|
||
{ col: 20, row: 6 },
|
||
{ col: 19, row: 7 },
|
||
],
|
||
},
|
||
];
|
||
|
||
/**
|
||
* Get a 3v3 team map by id or random.
|
||
* @param {number} [mapId] - Optional map id
|
||
* @returns {object} Team map data (deep clone).
|
||
*/
|
||
function getTeamMap(mapId) {
|
||
let map;
|
||
if (mapId) {
|
||
map = TEAM_MAPS.find((m) => m.id === mapId);
|
||
}
|
||
if (!map) {
|
||
map = TEAM_MAPS[Math.floor(Math.random() * TEAM_MAPS.length)];
|
||
}
|
||
return JSON.parse(JSON.stringify(map));
|
||
}
|
||
|
||
module.exports = { LEVELS, getLevelData, PVP_MAPS, getPvpMap, TEAM_MAPS, getTeamMap };
|