~comcloudway/little_town

8b872c4318c812d593daf31b70126455d890ba8f — Jakob Meier 1 year, 8 months ago ebcefd4
Started working on inventory
7 files changed, 124 insertions(+), 265 deletions(-)

M src/blocks.rs
M src/main.rs
D src/screens/#
M src/screens/build.rs
A src/screens/inventory.rs
M src/screens/mod.rs
M src/types.rs
M src/blocks.rs => src/blocks.rs +1 -0
@@ 2,6 2,7 @@ use macroquad::prelude::*;
use crate::textures::AssetStore;
use crate::types::Direction;

#[derive(Eq, Hash, Clone, PartialEq)]
pub enum Block {
    Dirt(Direction),
    GrassCenter(Direction)

M src/main.rs => src/main.rs +2 -21
@@ 10,34 10,15 @@ use types::{
    GameEvent
};
use textures::AssetStore;
use screens::welcome::WelcomeScreen;
use screens::build::BuildScreen;
use screens::Screen;

enum Screen {
    Welcome(WelcomeScreen),
    Build(BuildScreen)
}
impl GameComponent for Screen {
    fn draw(&self, assets: &AssetStore) {
        match self {
            Screen::Welcome(w) => w.draw(&assets),
            Screen::Build(b) => b.draw(&assets)
        }
    }
    fn ev_loop(&mut self) -> GameEvent {
        match self {
            Screen::Welcome(w) => w.ev_loop(),
            Screen::Build(b) => b.ev_loop()
        }
    }
}


#[macroquad::main("Little Town")]
async fn main() {
    let assets = AssetStore::init().await;

    let mut screen = Screen::Welcome(WelcomeScreen::new());
    let mut screen = Screen::default();

    loop {
        clear_background(Color::from_rgba(215, 189, 165, 255));

D src/screens/# => src/screens/# +0 -238
@@ 1,238 0,0 @@
use macroquad::prelude::*;
use std::collections::HashMap;
use crate::textures::AssetStore;
use crate::types::{
    GameComponent,
    GameEvent,
    Pos3
};
use crate::blocks::Block;

struct Camera {
    center: Vec2,
    scale: f32
}
impl Camera {
    fn new() -> Self {
        Self {
            center: Vec2::new(0.0, 0.0),
            scale: 1.0
        }
    }
}

/// image width
const TEXTURE_WIDTH: f32 = 256.0;
/// actual width inside ob cube
const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216
/// height of image
const TEXTURE_HEIGHT: f32 = 352.0;
/// empty space above texture
const TEXTURE_Y_WHITESPACE: f32 = 130.0;
/// actual height of cube
const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110
/// height of top surface
const TEXTURE_DEPTH: f32 = 100.0;

pub struct BuildScreen {
    grid: HashMap<Pos3, Block>,
    cam: Camera,
    mouse_down: bool
}
impl BuildScreen {
    pub fn new() -> Self {
        let mut this = Self {
            grid: HashMap::new(),
            cam: Camera::new(),
            mouse_down: false
        };

        this.grid.insert(Pos3::new(0, 0, 0), Block::Dirt(crate::types::Direction::North));
        this.grid.insert(Pos3::new(1, 0, 0), Block::Dirt(crate::types::Direction::North));
        this.grid.insert(Pos3::new(2, 0, 0), Block::Dirt(crate::types::Direction::North));
        this.grid.insert(Pos3::new(2, 1, 0), Block::Dirt(crate::types::Direction::North));
        this.grid.insert(Pos3::new(2, 1, 1), Block::Dirt(crate::types::Direction::North));

        this
    }
}

fn get_screen_coords(pos: &Pos3, scale: f32, center: Vec2) -> (f32, f32) {
    let w_i = TEXTURE_INNER_WIDTH * scale;
            let width = TEXTURE_WIDTH * scale;
            let height = TEXTURE_HEIGHT * scale;
            let h_i = TEXTURE_INNER_HEIGHT * scale;

            let dx = pos.y - pos.x;
            let dy = pos.y + pos.x;

            let x = screen_width() / 2.0
                + dx as f32 * w_i / 2.0
                + center.x
                - width / 2.0;
            let y = screen_height() / 2.0
                + dy as f32 * h_i / 2.0
                - pos.z as f32 * h_i
                + center.y
                - height / 2.0;

    return (x,y);
}

/// A block's face
///   /'\
///  / A \
/// |\   /|
/// | \./ |
/// \ B|C |
///  \.|./
///  A - Top
///  B- Left
///  C -Right
enum Face {
    Top,
    Left,
    Right
}
impl Face {
    /// gets the face (if any) that was clicked
    /// coordinates have to be normalized,
    /// the top left should be (0,0)
    fn from_xy(x: f32, y: f32) -> Option<Self> {

        // test B face
        {
            let ox = (TEXTURE_WIDTH / 2.0 - x).abs();
            let oy = (TEXTURE_Y_WHITESPACE + TEXTURE_DEPTH / 2.0 - y).abs();

            let y_max = TEXTURE_DEPTH / 2.0;
            let x_max = TEXTURE_WIDTH / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);
            let cy = grow * ox + y_max;

            if cy <= oy {
                return Some(Face::Top);
            }

        }

        None
    }
}

impl GameComponent for BuildScreen {
    fn draw(&self, assets: &AssetStore) {
        // order: x (inc) -> y (inc) -> z (inc)
        let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
        render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );

        for (pos, block) in render_order.iter() {
            let texture = block.get_texture(&assets);
            let width = TEXTURE_WIDTH * self.cam.scale;
            let height = TEXTURE_HEIGHT * self.cam.scale;

            let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);

            if x >= -width && x <= screen_width()
                && y >= -width && y <= screen_height() {
                    // render block
                    draw_texture_ex(
                        texture,
                        x,
                        y,
                        WHITE,
                        DrawTextureParams {
                            dest_size: Some(Vec2::new(width, height)),
                            source: None,
                            rotation: 0.0,
                            flip_x: false,
                            flip_y: false,
                            pivot: Some(Vec2::new(width / 2.0, height / 2.0))
                        });
                }
        }
    }
    fn ev_loop(&mut self) -> GameEvent {
        // mouse input
        if is_mouse_button_down(MouseButton::Left) {
            // currently holding button down
            self.mouse_down = true;
        } else if self.mouse_down {
            // button was released
            self.mouse_down = false;
            // determine which block / side was clicked
            let (mx, my) = mouse_position();

            // virtual render cycle
            let render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
            // no need to sort the first time
            //render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );

            // list of positions in render que for given pixel
            // (mx, my)
            let mut in_path:Vec<&Pos3> = Vec::new();

            for (pos, _) in render_order.iter() {
                let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);

                if mx >= x
                    && mx <= x + TEXTURE_WIDTH
                    && my >= y + TEXTURE_Y_WHITESPACE
                    && my <= y + TEXTURE_HEIGHT {

                        // check if mouse is above transparent area
                        // and skip block if necessary
                        if Face::from_xy(mx-x, my-y).is_none() {
                            println!("skipping");
                            continue;
                        }

                        // block in mouse path
                        in_path.push(pos);
                    }
            }
            in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 );
            if let Some(pos) = in_path.last() {
                // position of clicked block
                // because it is the last block in de render queue
                // for a given pixel
                let pos = Pos3::new(pos.x, pos.y, pos.z);
                let (x,y) = get_screen_coords(&pos, self.cam.scale, self.cam.center);
                let face = Face::from_xy(mx-x, my-y);
                println!("{:?}", face);

                // TODO: determine side
                self.grid.insert(pos, Block::GrassCenter(crate::types::Direction::West));
            }
        }

        // zoom with Ctrl-MouseWheel
        if is_key_down(KeyCode::LeftControl) {
            let mut scale = self.cam.scale;
            let (_, wy) = mouse_wheel();

            scale += wy as f32 * 1.0/10.0;

            if scale > 0.05 && scale < 6.0 {
                self.cam.scale = scale;
            }
        }

        // keyboard control
        if is_key_down(KeyCode::Down) {
            self.cam.center.y += 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Up) {
            self.cam.center.y -= 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Left) {
            self.cam.center.x -= 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Right) {
            self.cam.center.x += 1.0 * (1.0/self.cam.scale);
        }

        GameEvent::None
    }
}

M src/screens/build.rs => src/screens/build.rs +29 -3
@@ 7,12 7,17 @@ use crate::types::{
    Pos3
};
use crate::blocks::Block;
use super::inventory::Inventory;

/// the game camera
struct Camera {
    /// the centered pixel
    center: Vec2,
    /// scales the texture
    scale: f32
}
impl Camera {
    /// initialize new camera
    fn new() -> Self {
        Self {
            center: Vec2::new(0.0, 0.0),


@@ 37,14 42,18 @@ const TEXTURE_DEPTH: f32 = 100.0;
pub struct BuildScreen {
    grid: HashMap<Pos3, Block>,
    cam: Camera,
    mouse_down: bool
    mouse_down: bool,
    show_inv: bool,
    inv: Inventory
}
impl BuildScreen {
    pub fn new() -> Self {
        let mut this = Self {
            grid: HashMap::new(),
            cam: Camera::new(),
            mouse_down: false
            mouse_down: false,
            show_inv: false,
            inv: Inventory::new()
        };

        this.grid.insert(Pos3::new(0, 0, 0), Block::Dirt(crate::types::Direction::North));


@@ 145,6 154,10 @@ impl Face {

impl GameComponent for BuildScreen {
    fn draw(&self, assets: &AssetStore) {
        if self.show_inv {
            // TODO draw inventory
            return;
        }
        // order: x (inc) -> y (inc) -> z (inc)
        let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
        render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );


@@ 234,7 247,10 @@ impl GameComponent for BuildScreen {
                    }
                }

                self.grid.insert(pos, Block::GrassCenter(crate::types::Direction::West));
                if let Some(block) = self.inv.place() {
                    self.grid.insert(pos, block);
                }

            }
        }



@@ 264,6 280,16 @@ impl GameComponent for BuildScreen {
            self.cam.center.x += 1.0 * (1.0/self.cam.scale);
        }

        if is_key_down(KeyCode::I) {
            self.show_inv = true;
            match self.inv.ev_loop() {
                GameEvent::Quit => {
                    self.show_inv = false;
                }
                _ => ()
            }
        }

        GameEvent::None
    }
}

A src/screens/inventory.rs => src/screens/inventory.rs +55 -0
@@ 0,0 1,55 @@
use macroquad::prelude::*;
use crate::types::{
    GameComponent,
    GameEvent
};
use crate::textures::AssetStore;
use crate::blocks::Block;
use std::collections::HashMap;

pub struct Inventory {
    /// when set to true, the player has an infinite amount of items
    infinite_items: bool,
    /// items in inventory
    /// with their appropriate amount
    /// if amount is zero, the item should be removed
    /// if infinite_items is true, the amount wont decrease
    contents: HashMap<Block, usize>,
    /// which block is currently selected
    pub selected: Option<Block>
}
impl Inventory {
    pub fn new() -> Self {
        Self {
            infinite_items: true,
            contents: HashMap::new(),
            selected: None
        }
    }
    pub fn place(&mut self) -> Option<Block> {
        if let Some(block) = &self.selected {

            if !self.infinite_items {
                let cv: &usize = self.contents.get(&block).unwrap_or(&0);

                if cv > &0 {
                    *self.contents.get_mut(&block).unwrap_or(&mut 0) -= 1;
                } else {
                    self.contents.remove(&block);
                    return None;
                }
            }

            return Some(block.clone());
        }

        return None;
    }
}
impl GameComponent for Inventory {
    fn draw(&self, assets: &AssetStore) {
    }
    fn ev_loop(&mut self) -> GameEvent {
        GameEvent::None
    }
}

M src/screens/mod.rs => src/screens/mod.rs +36 -2
@@ 1,2 1,36 @@
pub mod welcome;
pub mod build;
mod welcome;
mod build;
mod inventory;

use welcome::WelcomeScreen;
use build::BuildScreen;

use crate::types::{
    GameComponent,
    GameEvent
};
use crate::textures::AssetStore;

pub enum Screen {
    Welcome(WelcomeScreen),
    Build(BuildScreen),
}
impl GameComponent for Screen {
    fn draw(&self, assets: &AssetStore) {
        match self {
            Screen::Welcome(w) => w.draw(&assets),
            Screen::Build(b) => b.draw(&assets),
        }
    }
    fn ev_loop(&mut self) -> GameEvent {
        match self {
            Screen::Welcome(w) => w.ev_loop(),
            Screen::Build(b) => b.ev_loop(),
        }
    }
}
impl Default for Screen {
    fn default() -> Self {
        Self::Welcome(WelcomeScreen::new())
    }
}

M src/types.rs => src/types.rs +1 -1
@@ 25,7 25,7 @@ impl Pos3 {
}

/// Directions
#[derive(PartialEq)]
#[derive(Eq, Hash, Clone, PartialEq)]
pub enum Direction {
    /// Left
    /// (left edge of screen)