From 546fcef57a4656ab2f42aee3d859bd7d1709d64b Mon Sep 17 00:00:00 2001 From: dani <> Date: Fri, 4 Aug 2023 22:41:17 +0000 Subject: [PATCH] started map editor --- Cargo.toml | 2 +- src/gsa_render_to_screen.rs | 40 ++++++----- src/main.rs | 59 ++++++++++++++++ src/mapedit.rs | 131 ++++++++++++++++++++++++++++++++++++ src/run.rs | 10 ++- 5 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 src/mapedit.rs diff --git a/Cargo.toml b/Cargo.toml index 0612e78..22e3440 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ gif = "0.12.0" clap = {version = "4.3.8", features = ["derive", "cargo"]} dunce = "1.0.4" path-slash = "0.2.1" +serde = "1.0.181" [profile.release-dani] inherits = "release" @@ -30,4 +31,3 @@ lto = true codegen-units = 1 strip = true panic = "abort" - diff --git a/src/gsa_render_to_screen.rs b/src/gsa_render_to_screen.rs index 07fd445..510c139 100644 --- a/src/gsa_render_to_screen.rs +++ b/src/gsa_render_to_screen.rs @@ -5,26 +5,33 @@ use crate::{ use glam::IVec2; use softbuffer::Buffer; -fn draw_tile(target: &mut [u8], pos: IVec2, tile: u16, tileset: &[u8], half: bool) { +fn draw_tile( + target: &mut [u8], + pos: IVec2, + tile: u16, + tileset: &[u8], + half: bool, + screen_size: IVec2, +) { let tilesize = if half { HALF_TILE_SIZE } else { TILE_SIZE }; let tx = tile as usize % 0x100 * tilesize; let ty = tile as usize / 0x100 * tilesize; - let startx = pos.x.max(0).min(SCREEN_WIDTH as i32); - let starty = pos.y.max(0).min(SCREEN_HEIGHT as i32); - let endx = (pos.x + tilesize as i32).max(0).min(SCREEN_WIDTH as i32); - let endy = (pos.y + tilesize as i32).max(0).min(SCREEN_HEIGHT as i32); + let startx = pos.x.max(0).min(screen_size.x); + let starty = pos.y.max(0).min(screen_size.y); + let endx = (pos.x + tilesize as i32).max(0).min(screen_size.x); + let endy = (pos.y + tilesize as i32).max(0).min(screen_size.y); for y in (starty - pos.y)..(endy - pos.y) { for x in (startx - pos.x)..(endx - pos.x) { let p = tileset[(x as usize + tx) + (y as usize + ty) * (TILESET_SIZE * TILE_SIZE)]; if p != TRANSPARENT { target[(x as usize + pos.x as usize) - + (y as usize + pos.y as usize) * SCREEN_WIDTH] = p; + + (y as usize + pos.y as usize) * screen_size.x as usize] = p; } } } } -fn render_map(target: &mut [u8], map: &Background, tileset: &[u8]) { +fn render_map(target: &mut [u8], map: &Background, tileset: &[u8], screen_size: IVec2) { let tcmult = if map.half_tile { 2 } else { 1 }; let tilesize = if map.half_tile { HALF_TILE_SIZE @@ -33,8 +40,10 @@ fn render_map(target: &mut [u8], map: &Background, tileset: &[u8]) { } as i32; let mut startx = map.scroll.x / tilesize; let mut starty = map.scroll.y / tilesize; - let endx = (BACKGROUND_MAX_SIZE as i32).min(startx + 20 * tcmult); - let endy = (BACKGROUND_MAX_SIZE as i32).min(starty + 12 * tcmult); + let endx = + (BACKGROUND_MAX_SIZE as i32).min(startx + (screen_size.x / TILE_SIZE as i32 + 1) * tcmult); + let endy = + (BACKGROUND_MAX_SIZE as i32).min(starty + (screen_size.y / TILE_SIZE as i32 + 1) * tcmult); startx = 0.max(startx); starty = 0.max(starty); for x in startx..endx { @@ -50,18 +59,19 @@ fn render_map(target: &mut [u8], map: &Background, tileset: &[u8]) { tile, tileset, map.half_tile, + screen_size, ); } } } } -pub(crate) fn render_to_screen(target: &mut [u8], gsa: &Gsa, tileset: &[u8]) { +pub(crate) fn render_to_screen(target: &mut [u8], gsa: &Gsa, tileset: &[u8], screen_size: IVec2) { for i in 0..MAX_BACKGROUNDS { - render_map(target, &gsa.bgs[i], tileset); + render_map(target, &gsa.bgs[i], tileset, screen_size); for sprite in &gsa.sprites { if sprite.tile != EMPTY_TILE && sprite.priority == i as u8 { - draw_tile(target, sprite.pos, sprite.tile, tileset, false); + draw_tile(target, sprite.pos, sprite.tile, tileset, false, screen_size); } } } @@ -77,14 +87,14 @@ pub(crate) fn render_to_window( off_y: usize, ) { let start = window_size.x as usize * off_y; - let end = start + window_size.x as usize * SCREEN_HEIGHT * scale; + let end = start + window_size.x as usize * window_size.y as usize * scale; for (y, row) in target[start..end] .chunks_exact_mut(window_size.x as usize * scale) .enumerate() { let y = y as i32; - for x in 0..SCREEN_WIDTH { - let p = src[x + y as usize * SCREEN_WIDTH]; + for x in 0..window_size.x as usize { + let p = src[x + y as usize * window_size.x as usize]; for scaley in 0..scale { for scalex in 0..scale { diff --git a/src/main.rs b/src/main.rs index 72a173d..25c933f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,61 @@ +mod background; +mod buttons; +mod gsa; +mod gsa_render_to_screen; +mod mapedit; +mod rgb; +mod run; +mod sprite; +mod tileset; + +//todo: figure out how to not repeat all the lib.rs stuff :( + +pub use crate::background::*; +pub use crate::buttons::*; +pub use crate::gsa::*; +pub use crate::rgb::*; +pub use crate::run::run; +pub use crate::sprite::*; + +/// Amount of sprites in [Gsa::sprites] +pub const MAX_SPRITES: usize = 0xff; + +/// Screen Width in pixels +pub const SCREEN_WIDTH: usize = 304; + +/// Screen Height in pixels +pub const SCREEN_HEIGHT: usize = 176; + +/// X and y dimensions of maps in [Gsa::bgs] +pub const BACKGROUND_MAX_SIZE: usize = 1024; + +/// Tile considered empty (never drawn even if has contents) +pub const EMPTY_TILE: u16 = 0xffff; + +/// Tile id of bold default font +pub const FONT_BOLD: u16 = 0xf000; + +/// Tile id of thin default font +pub const FONT_THIN: u16 = 0xe000; + +/// Width and height (in tiles) of tileset +pub const TILESET_SIZE: usize = 0x100; + +/// Width and height of a tile +pub const TILE_SIZE: usize = 16; + +/// Palette index which is treated as transparent +pub const TRANSPARENT: u8 = 0xff; + +/// Width and height of a tile in half-tile mode +pub const HALF_TILE_SIZE: usize = 8; + +/// Amount of tile maps in [Gsa::bgs] +pub const MAX_BACKGROUNDS: usize = 4; + use clap::{crate_name, crate_version, Parser, Subcommand}; use dunce::canonicalize; +use mapedit::run_mapedit; use path_slash::PathExt; use std::fs; use std::fs::{write, OpenOptions}; @@ -26,6 +82,8 @@ enum Cmd { #[arg(long)] overwrite: bool, }, + /// Edit map of current project + MapEdit {}, } fn main() { @@ -102,5 +160,6 @@ fn main() { ) .unwrap(); } + Cmd::MapEdit {} => run_mapedit(), } } diff --git a/src/mapedit.rs b/src/mapedit.rs new file mode 100644 index 0000000..e1000e9 --- /dev/null +++ b/src/mapedit.rs @@ -0,0 +1,131 @@ +use std::fs; +use std::num::NonZeroU32; + +use crate::gsa::Gsa; +use crate::gsa_render_to_screen::render_to_screen; +use crate::gsa_render_to_screen::render_to_window; +use crate::sprite::Sprite; +use crate::tileset::*; +use crate::*; +use clap::crate_version; +use glam::IVec2; +use winit::{ + dpi::LogicalSize, + event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +pub(crate) fn run_mapedit() { + println!("running map edit"); + + let tileset_path = "examples/basic/gfx.gif"; + let (tileset, palette) = load_tileset(&fs::read(tileset_path).unwrap()); + + let mut gsa = Gsa { + sprites: [Sprite::default(); MAX_SPRITES], + palette, + bgs: Default::default(), + font: FONT_BOLD, + pressed: 0, + released: 0, + down: 0, + }; + + gsa.reset_bgs(); + gsa.reset_sprites(); + + let event_loop = EventLoop::new(); + let size = LogicalSize::new(1280, 720); + let window = WindowBuilder::new() + .with_title(format!( + "Game Skunk Advance Map Editor v{}", + crate_version!() + )) + .with_inner_size(size) + .build(&event_loop) + .unwrap(); + + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); + window.request_redraw(); + let mut mouse_pos = IVec2::ZERO; + let mut left_down = false; + event_loop.run(move |event, _, control_flow| { + let mouse_pos = &mut mouse_pos; + let left_down = &mut left_down; + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape, ..), + .. + }, + .. + } => { + *control_flow = ControlFlow::Exit; + } + WindowEvent::MouseInput { state, button, .. } => match (state, button) { + (winit::event::ElementState::Pressed, winit::event::MouseButton::Left) => { + *left_down = true; + } + (winit::event::ElementState::Released, winit::event::MouseButton::Left) => { + *left_down = false; + } + _ => {} + }, + WindowEvent::CursorMoved { position, .. } => { + *mouse_pos = IVec2 { + x: position.x as i32, + y: position.y as i32, + }; + } + _ => {} + }, + + Event::MainEventsCleared => { + if *left_down { + let tile_x = mouse_pos.x as usize / TILE_SIZE; + let tile_y = mouse_pos.y as usize / TILE_SIZE; + gsa.bgs[0].tiles[tile_x][tile_y] = 0; + } + + // render + let size = window.inner_size(); + surface + .resize( + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ) + .unwrap(); + let mut screen_buffer = + vec![TRANSPARENT; size.width as usize * size.height as usize]; + let mut window_buffer = surface.buffer_mut().unwrap(); + render_to_screen( + &mut screen_buffer, + &gsa, + &tileset, + IVec2 { + x: size.width as i32, + y: size.height as i32, + }, + ); + render_to_window( + &mut window_buffer, + &mut screen_buffer, + &palette, + IVec2 { + x: size.width as i32, + y: size.height as i32, + }, + 1, + 0, + 0, + ); + window_buffer.present().unwrap(); + } + _ => {} + } + }); +} diff --git a/src/run.rs b/src/run.rs index b5f353c..f74c09c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -195,7 +195,15 @@ pub fn run( let mut screen_buffer = [TRANSPARENT; SCREEN_WIDTH * SCREEN_HEIGHT]; let mut window_buffer = surface.buffer_mut().unwrap(); - render_to_screen(&mut screen_buffer, &gsa, &tileset); + render_to_screen( + &mut screen_buffer, + &gsa, + &tileset, + IVec2 { + x: SCREEN_WIDTH as i32, + y: SCREEN_HEIGHT as i32, + }, + ); render_to_window( &mut window_buffer, &mut screen_buffer,