use rust_rl::*; use notan::prelude::*; use notan::draw::create_textures_from_atlas; use notan::draw::{ CreateFont, CreateDraw, DrawImages }; use specs::prelude::*; use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator }; use bracket_lib::prelude::*; use std::collections::HashMap; const TILESIZE: f32 = 16.0; const DISPLAYWIDTH: u32 = 100 * (TILESIZE as u32); const DISPLAYHEIGHT: u32 = 56 * (TILESIZE as u32); #[notan_main] fn main() -> Result<(), String> { let win_config = WindowConfig::new().set_size(DISPLAYWIDTH, DISPLAYHEIGHT).set_vsync(true); notan ::init_with(setup) .add_config(win_config) .add_config(notan::draw::DrawConfig) .draw(draw) .update(update) .build() } fn setup(gfx: &mut Graphics) -> State { let texture = gfx .create_texture() .from_image(include_bytes!("../resources/td.png")) .build() .unwrap(); let data = include_bytes!("../resources/td.json"); let atlas = create_textures_from_atlas(data, &texture).unwrap(); let mut gs = State { ecs: World::new(), base_texture: texture, atlas, mapgen_next_state: Some(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame, }), mapgen_index: 0, mapgen_history: Vec::new(), mapgen_timer: 0.0, }; gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.insert(SimpleMarkerAllocator::::new()); raws::load_raws(); // Insert calls gs.ecs.insert(RandomNumberGenerator::new()); gs.ecs.insert(map::MasterDungeonMap::new()); // Master map list gs.ecs.insert(Map::new(true, 1, 64, 64, 0, "New Map", "N", 0)); // Map gs.ecs.insert(Point::new(0, 0)); // Player pos gs.ecs.insert(gui::Ancestry::Human); // ancestry let player_entity = spawner::player(&mut gs.ecs, 0, 0); gs.ecs.insert(player_entity); // Player entity gs.ecs.insert(RunState::AwaitingInput {}); // TODO: Set this back to RunState::MapGen gs.ecs.insert(particle_system::ParticleBuilder::new()); gs.ecs.insert(rex_assets::RexAssets::new()); gamelog::setup_log(); gamelog::record_event(data::events::EVENT::Level(1)); gs.generate_world_map(1, TileType::Floor); gs } const ASCII_MODE: bool = false; // Change this to config setting const SHOW_BOUNDARIES: bool = false; // Config setting use notan::draw::Draw; enum DrawType { None, Visible, VisibleAndRemember, Telepathy, } #[derive(PartialEq, Eq, Hash)] struct DrawKey { x: i32, y: i32, render_order: i32, } struct DrawInfo { e: Entity, draw_type: DrawType, } fn draw_camera(ecs: &World, draw: &mut Draw, atlas: &HashMap) { let map = ecs.fetch::(); render_map_in_view(&*map, ecs, draw, atlas); { let bounds = crate::camera::get_screen_bounds(ecs); let positions = ecs.read_storage::(); let renderables = ecs.read_storage::(); let hidden = ecs.read_storage::(); let props = ecs.read_storage::(); let items = ecs.read_storage::(); let minds = ecs.read_storage::(); let pools = ecs.read_storage::(); let entities = ecs.entities(); let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::>(); let mut to_draw: HashMap = HashMap::new(); for (pos, render, e, _h) in data.iter() { let idx = map.xy_idx(pos.x, pos.y); let offset_x = pos.x - bounds.min_x + bounds.x_offset; let offset_y = pos.y - bounds.min_y + bounds.y_offset; if crate::camera::in_bounds( pos.x, pos.y, bounds.min_x, bounds.min_y, bounds.max_x, bounds.max_y ) { let draw_type = if map.visible_tiles[idx] { let is_prop = props.get(*e); let is_item = items.get(*e); if is_prop.is_some() || is_item.is_some() { // If it's a static entity, we want to draw it, and // also save it's location so that we remember where // it was last seen after it leaves vision. DrawType::VisibleAndRemember } else { // If it's anything else, just draw it. DrawType::Visible } } else if map.telepath_tiles[idx] { let has_mind = minds.get(*e); if has_mind.is_some() { // Mobs we see through telepathy - generally we just // draw these, but it uses a unique enum variant so // it can be treated differently if needed in future. DrawType::Telepathy } else { DrawType::None } } else { // If we don't see it, and we don't sense it with // telepathy, don't draw it at all. DrawType::None }; match draw_type { DrawType::None => {} _ => { to_draw.insert( DrawKey { x: offset_x, y: offset_y, render_order: render.render_order }, DrawInfo { e: *e, draw_type } ); } } } } let mut entries: Vec<(&DrawKey, &DrawInfo)> = to_draw.iter().collect(); entries.sort_by_key(|&(k, _v)| k.render_order); for entry in entries.iter() { match entry.1.draw_type { DrawType::Visible | DrawType::Telepathy => { if let Some(pool) = pools.get(entry.1.e) { if pool.hit_points.current < pool.hit_points.max { // Draw health bar } } draw.image(atlas.get("ui_heart_full").unwrap()).position( (entry.0.x as f32) * TILESIZE, (entry.0.y as f32) * TILESIZE ); // Draw entity } DrawType::VisibleAndRemember => { draw.image(atlas.get("ui_crystal_full").unwrap()).position( (entry.0.x as f32) * TILESIZE, (entry.0.y as f32) * TILESIZE ); } _ => {} } } } } fn render_map_in_view(map: &Map, ecs: &World, draw: &mut Draw, atlas: &HashMap) { let bounds = crate::camera::get_screen_bounds(ecs); for tile_y in bounds.min_y..bounds.max_y { for tile_x in bounds.min_x..bounds.max_x { if crate::camera::in_bounds(tile_x, tile_y, 0, 0, map.width, map.height) { let idx = map.xy_idx(tile_x, tile_y); if map.revealed_tiles[idx] { if ASCII_MODE { let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id( idx, &*map, Some(*ecs.fetch::()), None ); // TODO: Draw ASCII } else { let (id, tint) = crate::map::themes::get_sprite_for_id( idx, &*map, Some(*ecs.fetch::()) ); let px = idx_to_px( map.xy_idx(tile_x + bounds.x_offset, tile_y + bounds.y_offset), &map ); draw.image(atlas.get(id).unwrap()).position(px.0, px.1); } } } else if SHOW_BOUNDARIES { // TODO: Draw boundaries } } } } fn draw(app: &mut App, gfx: &mut Graphics, gs: &mut State) { let mut draw = gfx.create_draw(); draw.clear(Color::BLACK); match *gs.ecs.fetch::() { | RunState::MainMenu { .. } | RunState::CharacterCreation { .. } | RunState::PreRun { .. } => {} _ => { draw_camera(&gs.ecs, &mut draw, &gs.atlas); } } // Draw player (replace this with draw entities). let map = gs.ecs.fetch::(); let ppos = gs.ecs.fetch::(); let offsets = crate::camera::get_offset(); let px = idx_to_px(map.xy_idx(ppos.x + offsets.x, ppos.y + offsets.y), &map); draw.image(gs.atlas.get("ui_heart_full").unwrap()).position(px.0, px.1); // Render batch gfx.render(&draw); } fn idx_to_px(idx: usize, map: &Map) -> (f32, f32) { ( ((idx % (map.width as usize)) as i32 as f32) * (TILESIZE as f32), ((idx / (map.width as usize)) as i32 as f32) * (TILESIZE as f32), ) } fn update(ctx: &mut App, state: &mut State) { state.update(ctx); }