use super::{furniture::Furniture, grid::Grid}; use crate::game::{self, ZLayer}; use comfy::{ draw_rect, draw_rect_outline, draw_sprite, error, random_i32, texture_id, vec2, EngineContext, HashSet, RandomRange as _, Vec2, BLUE, GREEN, PURPLE, RED, WHITE }; use indexmap::IndexSet; pub const SCALE: f32 = 4.0; #[derive(Debug, PartialEq)] enum RoomType { Kitchen, Bath, Toilett, LivingRoom, SleepingRoom } #[derive(Debug)] struct Tile { pos: Vec2, size: Vec2, f: Furniture, z: i32 } #[derive(Debug)] pub struct Room { _room_type: RoomType, pub size: (u8, u8), //(width, height) pub grid: Grid, furnitures: Vec, human_pos: Option, human_nodes: Vec } impl RoomType { pub fn random() -> Self { match random_i32(0, 4) { 0 => RoomType::Kitchen, 1 => RoomType::Bath, 2 => RoomType::Toilett, 3 => RoomType::LivingRoom, 4 => RoomType::SleepingRoom, _ => panic!("Somehow you where unlucky and got a random number out of range") } } } impl Room { pub fn new(ctx: &mut EngineContext<'_>, with_human: bool) -> Self { let room_type = RoomType::random(); let size = Self::random_size(&room_type); let furnitures = Self::random_room_furniture(&room_type, size, ctx); let grid = Self::create_grid(size.0, size.1); let (human_pos, human_nodes) = if with_human { let mut human_pos = vec2(random_i32(1, size.0 as i32) as f32 + 0.5, 1.0f32); human_pos -= vec2(size.0 as f32 / 2.0, size.1 as f32 / 2.0); human_pos *= SCALE; let node_index_left = grid .nodes .iter() .enumerate() .filter(|(_i, &node)| node.y <= size.1 as f32 / 6.0) .filter(|(_i, &node)| node.x <= (human_pos.x - 0.5 * SCALE)) .max_by_key(|(_i, &node)| node.x as i32) .map(|(i, _)| i) .unwrap(); let node_index_right = grid .nodes .iter() .enumerate() .filter(|(_i, &node)| node.y <= size.1 as f32 / 6.0) .filter(|(_i, &node)| node.x >= (human_pos.x + 0.5 * SCALE)) .min_by_key(|(_i, &node)| node.x as i32) .map(|(i, _)| i) .unwrap(); ( Some(human_pos), (node_index_left ..= node_index_right).collect() ) } else { (None, Vec::new()) }; Room { _room_type: room_type, size, grid, furnitures, human_pos, human_nodes } } pub fn get_human_information(&self) -> (Option, &Vec) { (self.human_pos, &self.human_nodes) } fn random_size(room_type: &RoomType) -> (u8, u8) { //Kitchen + Living Room 5-8 //Bath + sleepingroom 4-6 //Toilet 2-3 match room_type { RoomType::Kitchen | RoomType::LivingRoom => (random_i32(5, 8) as u8, 3), RoomType::Bath | RoomType::SleepingRoom => (random_i32(4, 6) as u8, 3), RoomType::Toilett => (random_i32(2, 3) as u8, 3) } } fn random_room_furniture( room_type: &RoomType, (width, _height): (u8, u8), ctx: &mut EngineContext<'_> ) -> Vec { let mut furnitures = Vec::new(); let mut empty_spots: IndexSet = (0 .. width).collect(); fn random_empty_spot(empty_spots: &mut IndexSet) -> Option { if empty_spots.is_empty() { return None; } let random_idx = usize::gen_range(0, empty_spots.len()); empty_spots.swap_remove_index(random_idx) } fn random_empty_spot_size( empty_spots: &mut IndexSet, size: u8 ) -> Option { let mut empty_spots_size = IndexSet::::new(); for &index in empty_spots.iter() { let mut is_valid = true; for offset in 0 .. size { if !empty_spots.contains(&(index + offset)) { is_valid = false; break; } } if is_valid { empty_spots_size.insert(index); } } if empty_spots_size.is_empty() { return None; } let random_idx = usize::gen_range(0, empty_spots_size.len()); for offset in (0 .. size).rev() { empty_spots.swap_remove_index(random_idx + offset as usize); } Some(random_idx as u8) } fn random_appliance(empty_spots: &mut Vec) -> Option { if empty_spots.is_empty() { return None; } let random_idx = usize::gen_range(0, empty_spots.len()); Some(empty_spots.swap_remove(random_idx)) } const SIDEBOARD_HEIGHT: f32 = 0.1; const STOVE_HEIGHT: f32 = 0.025; const SINK_HEIGHT: f32 = 0.5; #[allow(clippy::single_match)] // we'll add more stuff later match room_type { RoomType::Kitchen => { // in a kitchen, we always add a fridge let fridge_pos = u8::gen_range(0, 2) * (width - 1); empty_spots.swap_remove(&fridge_pos); furnitures.push(Tile { pos: vec2(fridge_pos as f32, 0.0), size: vec2(1.0, 2.0), f: Furniture::new("kitchen", "fridge", ctx), z: 0 }); // and we always add an oven let Some(oven_pos) = random_empty_spot(&mut empty_spots) else { error!("How can I not fit an oven in a kitchen?!?"); return furnitures; }; furnitures.push(Tile { pos: vec2(oven_pos as f32, 0.0), size: vec2(1.0, 1.0), f: Furniture::new("kitchen", "oven", ctx), z: 0 }); // there's always sideboard above the oven with a stove furnitures.push(Tile { pos: vec2(oven_pos as f32, 1.0), size: vec2(1.0, SIDEBOARD_HEIGHT), f: Furniture::new("kitchen", "sideboard_1", ctx), z: 1 }); furnitures.push(Tile { pos: vec2(oven_pos as f32, 1.0 + SIDEBOARD_HEIGHT), size: vec2(1.0, STOVE_HEIGHT), f: Furniture::new("kitchen", "stove", ctx), z: 0 }); // and we always add a drawer that houses a sink let Some(sink_pos) = random_empty_spot(&mut empty_spots) else { error!("How can I not fit a sink in a kitchen?!?"); return furnitures; }; furnitures.push(Tile { pos: vec2(sink_pos as f32, 0.0), size: vec2(1.0, 1.0), f: Furniture::new("kitchen", "drawer_cupboard", ctx), z: 0 }); // there's always sideboard above that drawer with a sink **behind** it furnitures.push(Tile { pos: vec2(sink_pos as f32, 1.0), size: vec2(1.0, SINK_HEIGHT), f: Furniture::new("kitchen", "sink", ctx), z: 0 }); furnitures.push(Tile { pos: vec2(sink_pos as f32, 1.0), size: vec2(1.0, SIDEBOARD_HEIGHT), f: Furniture::new("kitchen", "sideboard_1", ctx), z: 1 }); // the current list of empty spots is the same list we can use to place // on-the-counter appliances later let mut empty_spots_clone = empty_spots.clone(); // build a list of the remaining kitchen appliances. we only want them // included once, most kitchens don't contain two washing machines etc let mut remaining_appliances: IndexSet<&'static str> = ["dishwasher", "dryer", "minifridge", "washing_machine"] .into_iter() .collect(); // let's add at most half of the remaining positions as big appliances for _ in 0 .. empty_spots.len() / 2 { let Some(asset) = random_empty_spot(&mut remaining_appliances) else { break; }; let Some(spot) = random_empty_spot(&mut empty_spots) else { error!("WTF I shouldn't've used more than half of the available spots"); return furnitures; }; furnitures.push(Tile { pos: vec2(spot as f32, 0.0), size: vec2(1.0, 1.0), f: Furniture::new("kitchen", asset, ctx), z: 0 }); furnitures.push(Tile { pos: vec2(spot as f32, 1.0), size: vec2(1.0, SIDEBOARD_HEIGHT), f: Furniture::new("kitchen", "sideboard_1", ctx), z: 1 }); } // and fill the remainder with drawers while !empty_spots.is_empty() { let asset = match u8::gen_range(0, 2) { 0 => "drawer", 1 => "drawer_cupboard", _ => unreachable!() }; let Some(spot) = random_empty_spot(&mut empty_spots) else { error!("WTF I should still have spots available"); return furnitures; }; furnitures.push(Tile { pos: vec2(spot as f32, 0.0), size: vec2(1.0, 1.0), f: Furniture::new("kitchen", asset, ctx), z: 0 }); furnitures.push(Tile { pos: vec2(spot as f32, 1.0), size: vec2(1.0, SIDEBOARD_HEIGHT), f: Furniture::new("kitchen", "sideboard_1", ctx), z: 1 }); } // build a list of on-the-counter kitchen appliances. we only want them // included once, most kitchens don't contain two toasters etc let mut remaining_appliances: Vec<(&'static str, f32, f32)> = [ ("blender", 0.3, 0.45), ("kettle", 0.3, 0.4), ("toaster", 0.5, 0.25) ] .into_iter() .collect(); // and then we fill like half the counter with appliances for _ in 0 .. empty_spots_clone.len() / 2 { let Some((asset, asset_w, asset_h)) = random_appliance(&mut remaining_appliances) else { break; }; let Some(spot) = random_empty_spot(&mut empty_spots_clone) else { error!("WTF I shouldn't've used more than half of the available spots"); return furnitures; }; furnitures.push(Tile { pos: vec2(spot as f32 + 0.5, 1.0 + SIDEBOARD_HEIGHT), size: vec2(asset_w, asset_h), f: Furniture::new("kitchen", asset, ctx), z: 0 }); } }, RoomType::LivingRoom => { let has_couch = match u8::gen_range(0, 2) { 0 => false, 1 => true, _ => unreachable!() }; if has_couch { if let Some(pos) = random_empty_spot_size(&mut empty_spots, 3) { furnitures.push(Tile { pos: vec2(pos as f32, 0.0), size: vec2(3.0, 1.0), f: Furniture::new("bedroom", "couch", ctx), z: 0 }); } } if let Some(pos) = random_empty_spot(&mut empty_spots) { furnitures.push(Tile { pos: vec2(pos as f32, 0.0), size: vec2(1.0, 2.0), f: Furniture::new("bedroom", "bookshelf", ctx), z: 0 }); } if let Some(pos) = random_empty_spot(&mut empty_spots) { furnitures.push(Tile { pos: vec2(pos as f32, 0.0), size: vec2(0.5, 0.9), f: Furniture::new("bedroom", "mini_ac", ctx), z: 0 }); } }, _ => {} } furnitures } fn create_grid(width: u8, height: u8) -> Grid { error!("START GRID CREATION!"); let left_border = width as f32 / 2.0; let lower_border = height as f32 / 2.0; //Lower Cable let lower_cable_y = height as f32 / 6.0; let mut current_x = -0.5; let mut nodes = Vec::new(); let max_offset = ((width / 2) as i32).max(1); while current_x < width as f32 { nodes.push(vec2( (current_x - left_border) * SCALE, (lower_cable_y - lower_border) * SCALE )); current_x += random_i32(1, max_offset) as f32; } current_x = width as f32 + 0.5; nodes.push(vec2( (current_x - left_border) * SCALE, (lower_cable_y - lower_border) * SCALE )); let mut connections = Vec::new(); for i in 1 .. nodes.len() { connections.push((i - 1, i)); } //Lamps let upper_cable_y = height as f32 - 0.25; let max_lamps = (width as f32 / 2.5).round() as i32; let lamp_amount = random_i32(1, max_lamps + 1); let node_indices: HashSet = (0 .. lamp_amount) .map(|_| random_i32(1, nodes.len() as i32 - 1) as usize) .collect(); let last_lower_node_index = nodes.len(); for (i, index) in node_indices.iter().enumerate() { nodes.push(vec2( nodes.get(*index).unwrap().x, (upper_cable_y - lower_border) * SCALE )); connections.push((*index, last_lower_node_index + i)); } Grid::new(nodes, connections) } pub fn draw(&self, human_layer: bool, magnet_layer: bool) { let (width, height) = self.size; draw_rect( vec2(0.0, 0.0), vec2(width as f32 * SCALE, height as f32 * SCALE), PURPLE, game::ZLayer::HumanLayer as i32 - 4 ); draw_rect_outline( vec2(0.0, 0.0), vec2(width as f32 * SCALE, height as f32 * SCALE), 0.3, RED, game::ZLayer::MapMax as i32 - 1 ); for tile in &self.furnitures { let mut pos = tile.pos - vec2(width as f32 / 2.0, height as f32 / 2.0); pos += tile.size * 0.5; if human_layer { if let Some(texture) = tile.f.get_human_texture_handle() { draw_sprite( texture, pos * SCALE, WHITE, game::ZLayer::HumanLayer as i32 + tile.z, tile.size * SCALE ); } else { draw_rect_outline( pos * SCALE, tile.size * SCALE, 0.3, GREEN, game::ZLayer::HumanLayer as i32 + tile.z ); } } if magnet_layer { if let Some(texture) = tile.f.get_magnet_texture_handle() { draw_sprite( texture, pos * SCALE, BLUE, game::ZLayer::MagneticLayer as i32 + tile.z, tile.size * SCALE ); } } if tile.f.is_on() { if let Some(texture) = tile.f.get_elec_texture_handle() { draw_sprite( texture, pos * SCALE, WHITE, game::ZLayer::ElectricLayer as i32 + tile.z, tile.size * SCALE ); } } } if human_layer { if let Some(human_pos) = self.human_pos { //draw_circle(human_pos, 0.5, RED, 20); draw_sprite( texture_id("human_house"), human_pos, WHITE, ZLayer::Human.into(), vec2(1.0, 2.0) * SCALE ); } } /* Debug draw for node_index in &self.human_nodes { draw_circle(*self.grid.nodes.get(*node_index).unwrap(), 0.5, GREEN, ZLayer::Human.into()); }*/ self.grid.draw(); } }