Merge branch 'main' into room-creation

This commit is contained in:
Glaeder 2024-07-07 00:04:54 +02:00
commit c8e21d544a
20 changed files with 1214 additions and 21 deletions

View file

@ -1,4 +1,4 @@
use comfy::*;
use comfy::{draw_circle, draw_line, error, vec2, Vec2, BLUE};
#[derive(Debug)]
pub struct Grid {

View file

@ -13,7 +13,7 @@ pub struct HouseState {
player: Player
}
pub fn draw(state: &crate::State, _engine: &comfy::EngineContext) {
pub fn draw(state: &crate::State, _engine: &comfy::EngineContext<'_>) {
//Draw House
state.house.room.draw();
@ -24,6 +24,6 @@ pub fn draw(state: &crate::State, _engine: &comfy::EngineContext) {
state.house.player.draw();
}
pub fn update(state: &mut crate::State, _engine: &mut comfy::EngineContext) {
pub fn update(state: &mut crate::State, _engine: &mut comfy::EngineContext<'_>) {
state.house.player.update(&state.house.room.grid);
}

View file

@ -1,6 +1,6 @@
use comfy::*;
use super::Grid;
use comfy::{delta, draw_circle, is_key_down, vec2, KeyCode, Vec2, RED};
use std::collections::HashSet;
#[derive(Debug)]
pub struct Player {

View file

@ -3,8 +3,8 @@ pub mod overworld;
#[derive(Debug, Default)]
pub enum Activity {
#[default]
House,
#[allow(dead_code)]
House,
#[default]
Overworld
}

View file

@ -1,7 +0,0 @@
use comfy::*;
pub fn draw(_state: &crate::State, _engine: &comfy::EngineContext) {
draw_circle(vec2(0.0, 0.0), 0.5, GREEN, 0);
}
pub fn update(_state: &mut crate::State, _engine: &mut comfy::EngineContext) {}

View file

@ -0,0 +1,31 @@
use comfy::{
draw_rect_outline, draw_sprite, main_camera_mut, EngineContext, IVec2, Vec2, RED,
WHITE
};
use crate::game::ZLayer;
pub mod worldgen;
pub fn draw(state: &crate::State, _engine: &comfy::EngineContext<'_>) {
for (coords, tile) in state.overworld.iter_tiles() {
for (i, texture) in tile.textures().iter().rev().enumerate() {
let i = i as i32;
draw_sprite(
*texture,
coords.as_vec2(),
WHITE,
ZLayer::MapMax - i,
Vec2::ONE
);
draw_rect_outline(coords.as_vec2(), Vec2::ONE, 0.1, RED, 10);
}
}
}
pub fn update(state: &mut crate::State, _engine: &mut EngineContext<'_>) {
let mut camera = main_camera_mut();
camera.center = Vec2::ZERO;
camera.zoom = 30.0;
state.overworld.get_or_generate_tile(IVec2::ZERO);
}

View file

@ -0,0 +1,453 @@
// TODO remove this
#![allow(dead_code)]
use crate::assets::ASSETS;
use comfy::{IVec2, TextureHandle, UVec2};
use log::info;
use std::collections::HashMap;
pub enum MovementCost {
/// No movement possible - cost infinitely high.
Infinite,
/// There is a path for this movement - movement is cheap.
Path,
/// There is no path and no obstacle.
Default,
/// There is an obstacle (i.e. fence) - movement is expensive.
Obstacle
}
#[derive(Clone, Copy, Debug)]
pub enum Tile {
Grass,
Path {
left: bool,
right: bool,
top: bool,
bottom: bool
},
Fence {
left: bool,
right: bool,
top: bool,
bottom: bool
},
House {
texture: TextureHandle,
door: bool
}
}
impl Tile {
pub fn textures(&self) -> Vec<TextureHandle> {
match self {
Self::Grass => vec![ASSETS.overworld.grass],
Self::Path {
left,
right,
top,
bottom
} => {
let path_texture = match (left, right, top, bottom) {
(true, true, false, false) => ASSETS.overworld.path_horiz,
(false, false, true, true) => ASSETS.overworld.path_vert,
(true, false, true, false) => ASSETS.overworld.path_top_left,
(true, false, false, true) => ASSETS.overworld.path_bottom_left,
(false, true, true, false) => ASSETS.overworld.path_top_right,
(false, true, false, true) => ASSETS.overworld.path_bottom_right,
(true, true, true, false) => ASSETS.overworld.path_top_left_right,
(true, true, false, true) => ASSETS.overworld.path_bottom_left_right,
(true, false, true, true) => ASSETS.overworld.path_top_left_bottom,
(false, true, true, true) => ASSETS.overworld.path_top_right_bottom,
(true, true, true, true) => ASSETS.overworld.path_crossing,
(true, false, false, false)
| (false, true, false, false)
| (false, false, true, false)
| (false, false, false, true) => panic!("We don't have no dead ends"),
(false, false, false, false) => panic!("I think you meant grass?!?")
};
vec![ASSETS.overworld.grass, path_texture]
},
Self::Fence {
left,
right,
top,
bottom
} => {
let path_texture = match (left, right, top, bottom) {
(true, true, false, false) => ASSETS.overworld.fence_horiz,
(false, false, true, true) => ASSETS.overworld.fence_vert,
(true, false, true, false) => ASSETS.overworld.fence_top_left,
(true, false, false, true) => ASSETS.overworld.fence_bottom_left,
(false, true, true, false) => ASSETS.overworld.fence_top_right,
(false, true, false, true) => ASSETS.overworld.fence_bottom_right,
(true, true, true, false)
| (true, true, false, true)
| (true, false, true, true)
| (false, true, true, true) => unimplemented!(),
(true, true, true, true) => unimplemented!(),
(true, false, false, false)
| (false, true, false, false)
| (false, false, true, false)
| (false, false, false, true) => panic!("We don't have no dead ends"),
(false, false, false, false) => panic!("I think you meant grass?!?")
};
vec![ASSETS.overworld.grass, path_texture]
},
Self::House { texture, .. } => {
vec![ASSETS.overworld.grass, *texture]
}
}
}
pub fn can_stand_inside(&self) -> bool {
unimplemented!()
}
pub fn movement_cost_left(&self) -> MovementCost {
unimplemented!()
}
pub fn movement_cost_right(&self) -> MovementCost {
unimplemented!()
}
pub fn movement_cost_up(&self) -> MovementCost {
unimplemented!()
}
pub fn movement_cost_down(&self) -> MovementCost {
unimplemented!()
}
pub fn can_enter_house(&self) -> bool {
unimplemented!()
}
}
/// The size of a chunk (both width and height). This value squared gives the amount of
/// tiles in the chunk.
const CHUNK_SIZE: u32 = 100;
/// Chunks
#[derive(Debug)]
#[allow(dead_code)]
pub struct Chunk {
/// All tiles within this chunk.
tiles: [Tile; (CHUNK_SIZE * CHUNK_SIZE) as usize],
/// All paths that leave this chunk on the left hand side.
paths_left: Vec<u32>,
/// All paths that leave this chunk on the right hand side.
paths_right: Vec<u32>,
/// All paths that leave this chunk on the bottom side.
paths_bottom: Vec<u32>,
/// All paths that leave this chunk on the top side.
paths_top: Vec<u32>
}
impl Chunk {
pub fn generate_chunk() -> Self {
info!("gen chunk");
// TODO real worldgen
// for the time being we just copy this pre-made house block into the chunk
fn house(texture: TextureHandle) -> Tile {
Tile::House {
texture,
door: false
}
}
let path_horiz = Tile::Path {
left: true,
right: true,
top: false,
bottom: false
};
let path_vert = Tile::Path {
left: false,
right: false,
top: true,
bottom: true
};
let path_crossing = Tile::Path {
left: true,
right: true,
top: true,
bottom: true
};
let fence_horiz = Tile::Fence {
left: true,
right: true,
top: false,
bottom: false
};
let fence_vert = Tile::Fence {
left: false,
right: false,
top: true,
bottom: true
};
let grass = Tile::Grass;
let block = [
[
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_crossing
],
[
Tile::Fence {
left: false,
right: true,
top: false,
bottom: true
},
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
Tile::Fence {
left: true,
right: false,
top: false,
bottom: true
},
path_vert
],
[
fence_vert,
grass,
grass,
grass,
house(ASSETS.overworld.house_roof_top),
grass,
grass,
grass,
grass,
grass,
fence_vert,
path_vert
],
[
fence_vert,
grass,
grass,
house(ASSETS.overworld.house_roof_mid_left),
house(ASSETS.overworld.house_mid_window),
house(ASSETS.overworld.house_roof_mid_right),
grass,
grass,
grass,
grass,
fence_vert,
path_vert
],
[
fence_vert,
grass,
house(ASSETS.overworld.house_roof_left),
house(ASSETS.overworld.house_mid_window),
house(ASSETS.overworld.house_mid),
house(ASSETS.overworld.house_mid_window),
house(ASSETS.overworld.house_roof_right),
grass,
grass,
grass,
fence_vert,
path_vert
],
[
fence_vert,
grass,
house(ASSETS.overworld.house_bottom_left),
house(ASSETS.overworld.house_bottom_door),
house(ASSETS.overworld.house_bottom_window),
house(ASSETS.overworld.house_bottom_window),
house(ASSETS.overworld.house_bottom_right),
grass,
grass,
grass,
fence_vert,
path_vert
],
[
fence_vert,
grass,
grass,
Tile::Path {
left: false,
right: true,
top: true,
bottom: false
},
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
path_horiz,
fence_vert,
Tile::Path {
left: true,
right: false,
top: true,
bottom: true
}
],
[
fence_vert, grass, grass, grass, grass, grass, grass, grass, grass,
grass, fence_vert, path_vert
],
[
fence_vert, grass, grass, grass, grass, grass, grass, grass, grass,
grass, fence_vert, path_vert
],
[
Tile::Fence {
left: false,
right: true,
top: true,
bottom: false
},
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
fence_horiz,
Tile::Fence {
left: true,
right: false,
top: true,
bottom: false
},
path_vert
]
];
// and then we copy this pre-made block into our chunk
let mut tiles = [Tile::Grass; (CHUNK_SIZE * CHUNK_SIZE) as usize];
for y in 0 .. CHUNK_SIZE {
for x in 0 .. CHUNK_SIZE {
let row = block[y as usize % block.len()];
tiles[(y * CHUNK_SIZE + x) as usize] = row[x as usize % row.len()];
}
}
Self {
tiles,
paths_left: vec![],
paths_right: vec![],
paths_bottom: vec![],
paths_top: vec![]
}
}
pub fn get_tile(&self, local_chunk_coords: UVec2) -> Option<&Tile> {
self.tiles
.get((local_chunk_coords.y * CHUNK_SIZE + local_chunk_coords.x) as usize)
}
/// iterate over all tiles and its local chunk coords
pub fn iter_tiles(&self) -> impl Iterator<Item = (UVec2, &Tile)> {
self.tiles.iter().enumerate().map(|(i, tile)| {
let i = i as u32;
(UVec2::new(i % CHUNK_SIZE, i / CHUNK_SIZE), tile)
})
}
}
#[derive(Debug, Default)]
pub struct Overworld {
chunks: HashMap<IVec2, Chunk>
}
fn world_to_chunk_and_local_coords(world_coords: IVec2) -> (IVec2, UVec2) {
let chunk_coords = IVec2 {
x: world_coords.x.div_euclid(CHUNK_SIZE as _),
y: world_coords.y.div_euclid(CHUNK_SIZE as _)
};
let local_chunk_coords = UVec2 {
x: world_coords.x.rem_euclid(CHUNK_SIZE as _) as _,
y: world_coords.y.rem_euclid(CHUNK_SIZE as _) as _
};
(chunk_coords, local_chunk_coords)
}
impl Overworld {
fn get_tile(&self, world_coords: IVec2) -> Option<&Tile> {
let (chunk_coords, local_chunk_coords) =
world_to_chunk_and_local_coords(world_coords);
let chunk = self.chunks.get(&chunk_coords)?;
chunk.get_tile(local_chunk_coords)
}
/// Return a [`Tile`] at the given world coordinates, or `None` if that tile has not
/// been generated yet. use engine/world cordinates.
pub fn get_or_generate_tile(&mut self, world_coords: IVec2) -> &Tile {
let mut coords = world_coords;
coords.y *= -1;
self.get_or_generate_tiles_private(coords)
}
fn get_or_generate_tiles_private(&mut self, world_coords: IVec2) -> &Tile {
let (chunk_coords, local_chunk_coords) =
world_to_chunk_and_local_coords(world_coords);
let chunk = self
.chunks
.entry(chunk_coords)
.or_insert_with(Chunk::generate_chunk);
chunk.get_tile(local_chunk_coords).unwrap()
}
/// Iterate over all generated tiles and its engine/world cordinates. using a [`Tile`] at the given world coordinates, or `None` if that tile has not
/// been generated yet.
pub fn iter_tiles(&self) -> impl Iterator<Item = (IVec2, &Tile)> {
self.iter_tilese_private().map(|(coords, tile)| {
let mut w_coords = coords;
w_coords.y *= -1;
(w_coords, tile)
})
}
/// iterate over all tiles and its global coords
fn iter_tilese_private(&self) -> impl Iterator<Item = (IVec2, &Tile)> {
self.chunks.iter().flat_map(|(chunk_coords, chunk)| {
chunk.iter_tiles().map(|(local_coords, tile)| {
// never fail because chunksize fits alswas into i32
let local_coords: IVec2 = local_coords.try_into().unwrap();
(local_coords + (*chunk_coords * CHUNK_SIZE as i32), tile)
})
})
}
}

View file

@ -1,20 +1,59 @@
use comfy::*;
use crate::{
activities::{house, overworld, Activity},
State
};
use comfy::EngineContext;
use std::ops::Sub;
pub fn update(state: &mut State, engine: &mut EngineContext) {
#[derive(Debug)]
pub struct Ghost {
/// current electric charge of the Ghost
pub charge: f32,
/// max electric charge of the Ghost
pub max_charge: f32
}
impl Default for Ghost {
fn default() -> Self {
Self {
charge: 1000.0,
max_charge: 1000.0
}
}
}
#[repr(i32)]
pub enum ZLayer {
MapMax = -1,
Human = 0,
Ghost = 1
}
impl From<ZLayer> for i32 {
fn from(value: ZLayer) -> Self {
// safe because #[repr(i32)]
value as i32
}
}
impl Sub<i32> for ZLayer {
type Output = i32;
fn sub(self, other: i32) -> Self::Output {
i32::from(self) - other
}
}
pub fn update(state: &mut State, engine: &mut EngineContext<'_>) {
match state.activity {
Activity::House => house::update(state, engine),
Activity::Overworld => overworld::update(state, engine)
}
}
pub fn draw(state: &State, engine: &EngineContext) {
pub fn draw(state: &State, engine: &EngineContext<'_>) {
match state.activity {
Activity::House => house::draw(state, engine),
Activity::Overworld => overworld::draw(state, engine)
}
crate::ui::draw(state, engine);
}

View file

@ -1,14 +1,33 @@
#![warn(rust_2018_idioms)]
#![deny(clippy::wildcard_imports)]
#![forbid(elided_lifetimes_in_paths, unsafe_code)]
mod assets {
include!(env!("ASSETS_RS"));
}
mod activities;
mod game;
mod ui;
use activities::{house::HouseState, Activity};
use comfy::*;
use self::{
activities::{house::HouseState, overworld::worldgen::Overworld, Activity},
assets::Assets,
game::Ghost
};
use comfy::{
init_game_config, pollster, run_comfy_main_async, EngineContext, EngineState,
GameConfig, GameLoop
};
const GAME_NAME: &str = "Powercreep";
#[derive(Debug, Default)]
struct State {
setup_called: bool,
activity: Activity,
ghost: Ghost,
overworld: Overworld,
house: HouseState
}
@ -17,7 +36,12 @@ impl GameLoop for State {
Self::default()
}
fn update(&mut self, engine: &mut EngineContext) {
fn update(&mut self, engine: &mut EngineContext<'_>) {
if !self.setup_called {
setup(engine);
self.setup_called = true;
}
game::update(self, engine);
game::draw(self, engine);
}
@ -27,6 +51,10 @@ fn config(config: GameConfig) -> GameConfig {
config
}
fn setup(ctx: &mut EngineContext<'_>) {
Assets::load(ctx);
}
async fn run() {
init_game_config(GAME_NAME.to_string(), env!("CARGO_PKG_VERSION"), config);
let mut engine = EngineState::new();

31
src/ui.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::State;
use comfy::{
draw_rect, draw_rect_outline, screen_height, screen_to_world, screen_width,
EngineContext, Vec2, BLUE, RED
};
pub fn draw(state: &State, _engine: &EngineContext<'_>) {
// seperate fill state into smaller section for better readability
let section_count = 5;
let mut start_positon = screen_to_world(Vec2::new(screen_width(), screen_height()));
// section size in world codinates
let section_size = Vec2::new(0.5, 0.25);
start_positon.x -= 0.5 * section_size.x + 0.5 * section_size.y;
start_positon.y += 0.5 * section_size.y + 0.5 * section_size.y;
// draw fill level
{
let ghost = &state.ghost;
let percent = ghost.charge / ghost.max_charge;
let mut size = section_size;
size.y = section_size.y * section_count as f32 * percent;
let mut position = start_positon;
position.y += 0.5 * -section_size.y + 0.5 * size.y;
draw_rect(position, size, BLUE, 100);
}
// draw sections
for i in 0 .. section_count {
let mut position = start_positon;
position.y += i as f32 * section_size.y;
draw_rect_outline(position, section_size, 0.1, RED, 100);
}
}