turtlegame/src/activities/house/room.rs
Dominic 7317f7514c
Some checks failed
Rust / rustfmt (push) Successful in 21s
Rust / clippy (push) Failing after 1m29s
Rust / build (push) Successful in 2m36s
cargofmt; uncommitted balancing
2024-07-08 12:56:27 +02:00

508 lines
13 KiB
Rust

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<Tile>,
human_pos: Option<Vec2>,
human_nodes: Vec<usize>
}
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<Vec2>, &Vec<usize>) {
(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<Tile> {
let mut furnitures = Vec::new();
let mut empty_spots: IndexSet<u8> = (0 .. width).collect();
fn random_empty_spot<T>(empty_spots: &mut IndexSet<T>) -> Option<T> {
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<u8>,
size: u8
) -> Option<u8> {
let mut empty_spots_size = IndexSet::<u8>::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<T>(empty_spots: &mut Vec<T>) -> Option<T> {
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<usize> = (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();
}
}