mirror of
https://github.com/edg-l/ddnet-map-gen.git
synced 2024-09-19 09:12:22 +00:00
more progress
This commit is contained in:
parent
8b1143de74
commit
ff49a772de
|
@ -1,7 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ddnet-map-gen"
|
name = "ddnet-map-gen"
|
||||||
|
authors = ["Edgar <git@edgarluque.com>"]
|
||||||
|
description = "A DDraceNetwork map generator."
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "AGPL-3-only"
|
||||||
|
keywords = ["ddnet", "teeworlds", "mapgen"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -630,7 +630,7 @@ state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) 2022 Edgar Luque
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
|
46
src/cli.rs
46
src/cli.rs
|
@ -5,12 +5,38 @@ use std::path::Path;
|
||||||
|
|
||||||
pub fn run_cli() -> Result<()> {
|
pub fn run_cli() -> Result<()> {
|
||||||
let matches = command!()
|
let matches = command!()
|
||||||
.about("A DDraceNetwork map generator.")
|
.about("A DDraceNetwork map generator")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.subcommand_required(true)
|
.subcommand_required(true)
|
||||||
.arg(arg!(<FILE> "The output file").required(true))
|
.arg(arg!(<FILE> "The output map file").required(true))
|
||||||
.subcommand(Command::new("maze").about("Generate a maze-like map."))
|
.subcommand(
|
||||||
.subcommand(Command::new("fly").about("Generate a map for fly techniques."))
|
Command::new("maze")
|
||||||
|
.about("Generate a maze-like map")
|
||||||
|
.arg(
|
||||||
|
arg!(--width <WIDTH> "The width of the map")
|
||||||
|
.default_value("1000")
|
||||||
|
.required(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg!(--height <HEIGHT> "The height of the map")
|
||||||
|
.default_value("1000")
|
||||||
|
.required(false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("fly")
|
||||||
|
.about("Generate a map for fly techniques")
|
||||||
|
.arg(
|
||||||
|
arg!(--width <WIDTH> "The width of the map")
|
||||||
|
.default_value("1000")
|
||||||
|
.required(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg!(--height <HEIGHT> "The height of the map")
|
||||||
|
.default_value("1000")
|
||||||
|
.required(false),
|
||||||
|
),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let output = matches.value_of("FILE").expect("output is required");
|
let output = matches.value_of("FILE").expect("output is required");
|
||||||
|
@ -19,8 +45,16 @@ pub fn run_cli() -> Result<()> {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("maze", _sub_m)) => MazeGenerator::save_file(&mut rng, output)?,
|
Some(("maze", sub_m)) => {
|
||||||
Some(("fly", _sub_m)) => FlyGenerator::save_file(&mut rng, output)?,
|
let width: usize = sub_m.value_of_t("width").unwrap_or_else(|e| e.exit());
|
||||||
|
let height: usize = sub_m.value_of_t("height").unwrap_or_else(|e| e.exit());
|
||||||
|
MazeGenerator::save_file(&mut rng, width, height, output)?
|
||||||
|
}
|
||||||
|
Some(("fly", sub_m)) => {
|
||||||
|
let width: usize = sub_m.value_of_t("width").unwrap_or_else(|e| e.exit());
|
||||||
|
let height: usize = sub_m.value_of_t("height").unwrap_or_else(|e| e.exit());
|
||||||
|
FlyGenerator::save_file(&mut rng, width, height, output)?
|
||||||
|
}
|
||||||
_ => panic!("invalid command"),
|
_ => panic!("invalid command"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,52 +6,58 @@ use rand::Rng;
|
||||||
pub struct FlyGenerator;
|
pub struct FlyGenerator;
|
||||||
|
|
||||||
impl MapGenerator for FlyGenerator {
|
impl MapGenerator for FlyGenerator {
|
||||||
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> {
|
fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap> {
|
||||||
let mut map = create_initial_map()?;
|
let mut map = create_initial_map()?;
|
||||||
|
|
||||||
const HEIGHT: usize = 1000;
|
let mut tiles = Array2::from_shape_simple_fn((height, width), || {
|
||||||
const WIDTH: usize = 100;
|
|
||||||
|
|
||||||
let mut tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || {
|
|
||||||
GameTile::new(TILE_EMPTY, TileFlags::empty())
|
GameTile::new(TILE_EMPTY, TileFlags::empty())
|
||||||
});
|
});
|
||||||
let mut front_tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || {
|
let mut front_tiles = Array2::from_shape_simple_fn((height, width), || {
|
||||||
GameTile::new(TILE_EMPTY, TileFlags::empty())
|
GameTile::new(TILE_EMPTY, TileFlags::empty())
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut unhookable_tiles =
|
let mut unhookable_tiles =
|
||||||
Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty()));
|
Array2::from_shape_simple_fn((height, width), || Tile::new(0, TileFlags::empty()));
|
||||||
let mut freeze_tiles =
|
let mut freeze_tiles =
|
||||||
Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty()));
|
Array2::from_shape_simple_fn((height, width), || Tile::new(0, TileFlags::empty()));
|
||||||
|
|
||||||
tiles
|
tiles
|
||||||
.row_mut(HEIGHT - 2)
|
.row_mut(height - 2)
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|tile| tile.id = TILE_UNHOOKABLE);
|
.for_each(|tile| tile.id = TILE_UNHOOKABLE);
|
||||||
unhookable_tiles
|
unhookable_tiles
|
||||||
.row_mut(HEIGHT - 2)
|
.row_mut(height - 2)
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|tile| tile.id = 1);
|
.for_each(|tile| tile.id = 2);
|
||||||
|
|
||||||
tiles[(HEIGHT - 3, WIDTH / 2)].id = TILE_SPAWN;
|
tiles[(height - 3, width / 2)].id = TILE_SPAWN;
|
||||||
|
|
||||||
for x in 0..WIDTH {
|
for x in 0..width {
|
||||||
front_tiles[(HEIGHT - 6, x)].id = TILE_START;
|
front_tiles[(height - 6, x)].id = TILE_START;
|
||||||
front_tiles[(10, x)].id = TILE_FINISH;
|
front_tiles[(10, x)].id = TILE_FINISH;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut center: i64 = WIDTH as i64 / 2;
|
let mut center: i64 = width as i64 / 2;
|
||||||
let mut fly_width: i64 = 10;
|
let mut fly_width: i64 = 10;
|
||||||
|
|
||||||
for y in (0..=(HEIGHT - 3)).rev() {
|
let max_steps = (height as f64 * 0.30f64).round() as i64;
|
||||||
let direction: i64 = rng.gen_range(-1..=1);
|
|
||||||
|
let mut direction_steps: i64 = rng.gen_range((max_steps / 2)..=max_steps);
|
||||||
|
let mut direction: i64 = rng.gen_range(-1..=1);
|
||||||
|
|
||||||
|
for y in (0..=(height - 3)).rev() {
|
||||||
|
if direction_steps == 0 {
|
||||||
|
direction_steps = rng.gen_range(1..=10);
|
||||||
|
direction = rng.gen_range(-1..=1);
|
||||||
|
}
|
||||||
|
|
||||||
let width_change: i64 = rng.gen_range(-1..=1);
|
let width_change: i64 = rng.gen_range(-1..=1);
|
||||||
center += direction;
|
center += direction;
|
||||||
fly_width += width_change;
|
fly_width += width_change;
|
||||||
center = center.clamp(fly_width, WIDTH as i64 - fly_width);
|
fly_width = fly_width.clamp(3, 12);
|
||||||
fly_width = fly_width.clamp(2, 12);
|
center = center.clamp(fly_width, width as i64 - fly_width - 1);
|
||||||
|
|
||||||
for x in ((center + fly_width) as usize)..WIDTH {
|
for x in ((center + fly_width) as usize)..width {
|
||||||
tiles[(y, x)].id = TILE_FREEZE;
|
tiles[(y, x)].id = TILE_FREEZE;
|
||||||
freeze_tiles[(y, x)].id = 4;
|
freeze_tiles[(y, x)].id = 4;
|
||||||
}
|
}
|
||||||
|
@ -60,6 +66,8 @@ impl MapGenerator for FlyGenerator {
|
||||||
tiles[(y, x)].id = TILE_FREEZE;
|
tiles[(y, x)].id = TILE_FREEZE;
|
||||||
freeze_tiles[(y, x)].id = 4;
|
freeze_tiles[(y, x)].id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
direction_steps -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let game_layer = GameLayer {
|
let game_layer = GameLayer {
|
||||||
|
@ -70,11 +78,11 @@ impl MapGenerator for FlyGenerator {
|
||||||
tiles: CompressedData::Loaded(front_tiles),
|
tiles: CompressedData::Loaded(front_tiles),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut unhook_tiles_layer = TilesLayer::new((HEIGHT, WIDTH));
|
let mut unhook_tiles_layer = TilesLayer::new((height, width));
|
||||||
unhook_tiles_layer.image = Some(0);
|
unhook_tiles_layer.image = Some(0);
|
||||||
unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles);
|
unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles);
|
||||||
|
|
||||||
let mut freeze_tiles_layer = TilesLayer::new((HEIGHT, WIDTH));
|
let mut freeze_tiles_layer = TilesLayer::new((height, width));
|
||||||
freeze_tiles_layer.image = Some(1);
|
freeze_tiles_layer.image = Some(1);
|
||||||
freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles);
|
freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles);
|
||||||
freeze_tiles_layer.color = Color {
|
freeze_tiles_layer.color = Color {
|
||||||
|
|
|
@ -6,33 +6,55 @@ use ndarray::Array2;
|
||||||
pub struct MazeGenerator;
|
pub struct MazeGenerator;
|
||||||
|
|
||||||
impl MapGenerator for MazeGenerator {
|
impl MapGenerator for MazeGenerator {
|
||||||
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> {
|
fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap> {
|
||||||
let mut map = create_initial_map()?;
|
// Must be odd.
|
||||||
let maze = Maze::new(1001, 1001).unwrap().generate(rng);
|
let width = {
|
||||||
|
if width % 2 == 0 {
|
||||||
|
width + 1
|
||||||
|
} else {
|
||||||
|
width
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let height = {
|
||||||
|
if height % 2 == 0 {
|
||||||
|
height + 1
|
||||||
|
} else {
|
||||||
|
height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut tiles = Array2::from_shape_fn((1001, 1001), |(x, y)| {
|
let mut map = create_initial_map()?;
|
||||||
|
let maze = Maze::new(width, height).unwrap().generate(rng);
|
||||||
|
|
||||||
|
let hookable_tiles =
|
||||||
|
Array2::from_shape_fn((height, width), |(y, x)| {
|
||||||
|
let mut t = 0;
|
||||||
|
if maze[x][y] == 1 {
|
||||||
|
t = 9;
|
||||||
|
}
|
||||||
|
Tile::new(t, TileFlags::empty())
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tiles = Array2::from_shape_fn((width, height), |(y, x)| {
|
||||||
GameTile::new(maze[x][y], TileFlags::empty())
|
GameTile::new(maze[x][y], TileFlags::empty())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Put spawn and start tile on top left most tile.
|
// Put spawn and start tile on top left most tile.
|
||||||
let mut added_spawn = false;
|
'outerStart: for y in 0..height {
|
||||||
'outerStart: for y in 0..101 {
|
for x in 0..width {
|
||||||
for x in 0..101 {
|
let tile = &mut tiles[(y, x)];
|
||||||
let tile = &mut tiles[(x, y)];
|
if tile.id == 0 {
|
||||||
if tile.id == 0 && !added_spawn {
|
tile.id = TILE_SPAWN;
|
||||||
*tile = GameTile::new(TILE_SPAWN, TileFlags::empty());
|
replace_around_gametile(&mut tiles, x, y, TILE_EMPTY, TILE_START);
|
||||||
added_spawn = true;
|
|
||||||
} else if tile.id == 0 && added_spawn {
|
|
||||||
*tile = GameTile::new(TILE_START, TileFlags::empty());
|
|
||||||
break 'outerStart;
|
break 'outerStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put finish tile on bottom right most tile.
|
// Put finish tile on bottom right most tile.
|
||||||
'outerFinish: for y in (0..1001).rev() {
|
'outerFinish: for y in (0..height).rev() {
|
||||||
for x in (0..1001).rev() {
|
for x in (0..width).rev() {
|
||||||
let tile = &mut tiles[(x, y)];
|
let tile = &mut tiles[(y, x)];
|
||||||
if tile.id == 0 {
|
if tile.id == 0 {
|
||||||
*tile = GameTile::new(TILE_FINISH, TileFlags::empty());
|
*tile = GameTile::new(TILE_FINISH, TileFlags::empty());
|
||||||
break 'outerFinish;
|
break 'outerFinish;
|
||||||
|
@ -40,12 +62,19 @@ impl MapGenerator for MazeGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut hook_tiles_layer = TilesLayer::new((height, width));
|
||||||
|
hook_tiles_layer.image = Some(0);
|
||||||
|
hook_tiles_layer.tiles = CompressedData::Loaded(hookable_tiles);
|
||||||
|
|
||||||
let game_layer = GameLayer {
|
let game_layer = GameLayer {
|
||||||
tiles: CompressedData::Loaded(tiles),
|
tiles: CompressedData::Loaded(tiles),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut physics = Group::physics();
|
let mut physics = Group::physics();
|
||||||
physics.layers.push(Layer::Game(game_layer));
|
physics.layers.push(Layer::Game(game_layer));
|
||||||
|
physics.layers.push(Layer::Tiles(hook_tiles_layer));
|
||||||
|
|
||||||
|
map.groups.push(quads_sky());
|
||||||
map.groups.push(physics);
|
map.groups.push(physics);
|
||||||
|
|
||||||
Ok(map)
|
Ok(map)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
use ndarray::{Array2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use twmap::*;
|
use twmap::*;
|
||||||
|
|
||||||
|
@ -17,10 +18,10 @@ pub const TILE_FINISH: u8 = 34;
|
||||||
pub const TILE_SPAWN: u8 = 192;
|
pub const TILE_SPAWN: u8 = 192;
|
||||||
|
|
||||||
pub trait MapGenerator {
|
pub trait MapGenerator {
|
||||||
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap>;
|
fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap>;
|
||||||
|
|
||||||
fn save_file<R: Rng + ?Sized>(rng: &mut R, path: &Path) -> Result<()> {
|
fn save_file<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize, path: &Path) -> Result<()> {
|
||||||
let mut map = Self::generate(rng)?;
|
let mut map = Self::generate(rng, width, height)?;
|
||||||
map.save_file(path)?;
|
map.save_file(path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -81,3 +82,32 @@ pub fn quads_sky() -> Group {
|
||||||
quads_group.layers.push(Layer::Quads(quads_layer));
|
quads_group.layers.push(Layer::Quads(quads_layer));
|
||||||
quads_group
|
quads_group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changed the id of the tile if matches oldid.
|
||||||
|
pub fn replace_gametile(tiles: &mut Array2<GameTile>, x: usize, y: usize, oldid: u8, newid: u8) {
|
||||||
|
if tiles[(y, x)].id == oldid {
|
||||||
|
tiles[(y, x)].id = newid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_around_gametile(tiles: &mut Array2<GameTile>, x: usize, y: usize, oldid: u8, newid: u8) {
|
||||||
|
let width = tiles.ncols();
|
||||||
|
let height = tiles.nrows();
|
||||||
|
|
||||||
|
let directions = [-1, 0, 1];
|
||||||
|
|
||||||
|
for diry in directions {
|
||||||
|
for dirx in directions {
|
||||||
|
if dirx == 0 && diry == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (y as i64) + diry < 0 || (y as i64) + diry >= height as i64 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (x as i64) + dirx < 0 || (x as i64) + dirx >= width as i64 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
replace_gametile(tiles, ((x as i64) + dirx) as usize, ((y as i64) + diry) as usize, oldid, newid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue