diff --git a/AGENTS.md b/AGENTS.md index 6efc35a..433dd2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,13 +45,15 @@ Terminal (`--mode terminal`): GPU (`--mode ascii` / `--mode graphics`): - `a` / `d` or `←` / `→` — move - `w` / `↑` / `space` — jump -- `h` / `j` / `k` / `l` — shoot left / down / up / right +- `h` / `j` / `k` / `l` — shoot left / down / up / right (keyboard fallback) +- **Left-click** — shoot toward mouse cursor (Noita-style aiming) +- **Mouse position** — player faces mouse direction (left/right) - `f` — toggle fireball mode - `>` / `.` — descend when standing on stairs - `e` — use/equip first inventory item - `r` — drop first inventory item - `1`–`0` / `x` — paint material brush -- `y` / `u` / `i` / `o` — move camera +- `y` / `u` / `i` / `o` — move camera offset - `q` / `esc` — quit ## Shaders diff --git a/src/entity/body_template.rs b/src/entity/body_template.rs index 4527c54..bb3ebc0 100644 --- a/src/entity/body_template.rs +++ b/src/entity/body_template.rs @@ -35,60 +35,69 @@ pub struct BodyTemplate { impl BodyTemplate { pub fn humanoid_player() -> Self { let parts = vec![ - // Head: 3x2 = 6 + p!(-1, -6, 210, 185, 235, "head"), + p!(0, -6, 225, 205, 245, "head"), + p!(1, -6, 210, 185, 235, "head"), p!(-1, -5, 215, 190, 240, "head"), - p!(0, -5, 225, 205, 245, "head"), + p!(0, -5, 230, 210, 250, "head"), p!(1, -5, 215, 190, 240, "head"), - p!(-1, -4, 215, 190, 240, "head"), - p!(0, -4, 225, 205, 245, "head"), - p!(1, -4, 215, 190, 240, "head"), - // Hat brim: 5 wide + p!(-2, -5, 195, 165, 220, "head"), + p!(2, -5, 195, 165, 220, "head"), + p!(0, -5, 40, 30, 50, "eye"), + p!(-1, -5, 60, 40, 70, "eye"), + p!(1, -5, 60, 40, 70, "eye"), + p!(-2, -6, 120, 80, 40, "hair"), + p!(-1, -7, 140, 90, 50, "hair"), + p!(0, -7, 150, 100, 55, "hair"), + p!(1, -7, 140, 90, 50, "hair"), + p!(2, -6, 120, 80, 40, "hair"), + p!(-3, -7, 110, 70, 35, "hair"), + p!(3, -7, 110, 70, 35, "hair"), p!(-2, -4, 80, 50, 130, "hat"), p!(2, -4, 80, 50, 130, "hat"), - // Hat top: 3 - p!(-1, -6, 90, 55, 140, "hat"), - p!(0, -6, 100, 60, 150, "hat"), - p!(1, -6, 90, 55, 140, "hat"), - // Neck + shoulders: belt accent - p!(0, -3, 230, 190, 110, "belt"), + p!(-1, -5, 90, 55, 140, "hat"), + p!(0, -5, 100, 60, 150, "hat"), + p!(1, -5, 90, 55, 140, "hat"), + p!(0, -4, 230, 190, 110, "belt"), + p!(-1, -4, 150, 90, 210, "shirt"), + p!(1, -4, 150, 90, 210, "shirt"), + p!(-2, -4, 205, 180, 235, "hand"), + p!(2, -4, 205, 180, 235, "hand"), + p!(-3, -4, 195, 170, 225, "hand"), + p!(3, -4, 195, 170, 225, "hand"), p!(-1, -3, 150, 90, 210, "shirt"), + p!(0, -3, 160, 100, 220, "shirt"), p!(1, -3, 150, 90, 210, "shirt"), - // Arms out: hands - p!(-2, -3, 205, 180, 235, "hand"), - p!(2, -3, 205, 180, 235, "hand"), - p!(-3, -3, 195, 170, 225, "hand"), - p!(3, -3, 195, 170, 225, "hand"), - // Torso: 3 wide x 2 + p!(-2, -3, 210, 185, 240, "hand"), + p!(2, -3, 210, 185, 240, "hand"), p!(-1, -2, 150, 90, 210, "shirt"), p!(0, -2, 160, 100, 220, "shirt"), p!(1, -2, 150, 90, 210, "shirt"), + p!(-2, -2, 200, 170, 230, "hand"), + p!(2, -2, 200, 170, 230, "hand"), + p!(-3, -2, 80, 50, 130, "cape"), + p!(3, -2, 80, 50, 130, "cape"), p!(-1, -1, 150, 90, 210, "shirt"), p!(0, -1, 160, 100, 220, "shirt"), p!(1, -1, 150, 90, 210, "shirt"), - // Arms lower - p!(-2, -2, 210, 185, 240, "hand"), - p!(2, -2, 210, 185, 240, "hand"), - p!(-2, -1, 200, 170, 230, "hand"), - p!(2, -1, 200, 170, 230, "hand"), - // Belt + p!(-3, -1, 75, 45, 120, "cape"), + p!(3, -1, 75, 45, 120, "cape"), p!(-1, 0, 230, 190, 110, "belt"), p!(0, 0, 240, 200, 120, "belt"), p!(1, 0, 230, 190, 110, "belt"), - // Hips: 3 wide + p!(-3, 0, 70, 40, 110, "cape"), + p!(3, 0, 70, 40, 110, "cape"), p!(-1, 1, 95, 55, 155, "pants"), p!(0, 1, 95, 55, 155, "pants"), p!(1, 1, 95, 55, 155, "pants"), - // Upper legs: 3 wide p!(-1, 2, 85, 45, 145, "pants"), p!(0, 2, 85, 45, 145, "pants"), p!(1, 2, 85, 45, 145, "pants"), p!(-2, 2, 85, 45, 145, "pants"), p!(2, 2, 85, 45, 145, "pants"), - // Lower legs: split p!(-2, 3, 75, 40, 125, "pants"), p!(0, 3, 75, 40, 125, "pants"), p!(2, 3, 75, 40, 125, "pants"), - // Boots p!(-2, 4, 55, 30, 95, "boots"), p!(0, 4, 55, 30, 95, "boots"), p!(2, 4, 55, 30, 95, "boots"), @@ -109,42 +118,46 @@ impl BodyTemplate { pub fn humanoid_goblin() -> Self { let parts = vec![ - // Head: 3x2 - p!(-1, -5, 120, 200, 95, "head"), - p!(0, -5, 130, 210, 100, "head"), - p!(1, -5, 120, 200, 95, "head"), - p!(-1, -4, 120, 200, 95, "head"), - p!(0, -4, 130, 210, 100, "head"), - p!(1, -4, 120, 200, 95, "head"), - // Ears - p!(-2, -4, 110, 190, 85, "head"), - p!(2, -4, 110, 190, 85, "head"), - // Neck - p!(0, -3, 100, 180, 75, "skin"), - // Shoulders: rags + p!(-1, -6, 120, 200, 95, "head"), + p!(0, -6, 130, 210, 100, "head"), + p!(1, -6, 120, 200, 95, "head"), + p!(-1, -5, 125, 205, 97, "head"), + p!(0, -5, 135, 215, 102, "head"), + p!(1, -5, 125, 205, 97, "head"), + p!(-2, -5, 110, 190, 85, "head"), + p!(2, -5, 110, 190, 85, "head"), + p!(-1, -5, 200, 50, 50, "eye"), + p!(1, -5, 200, 50, 50, "eye"), + p!(-2, -5, 120, 80, 50, "ear"), + p!(2, -5, 120, 80, 50, "ear"), + p!(-3, -5, 110, 70, 40, "ear"), + p!(3, -5, 110, 70, 40, "ear"), + p!(-1, -4, 90, 180, 70, "tooth"), + p!(0, -4, 90, 180, 70, "tooth"), + p!(1, -4, 90, 180, 70, "tooth"), + p!(0, -4, 100, 180, 75, "skin"), + p!(-1, -4, 100, 180, 75, "skin"), + p!(1, -4, 100, 180, 75, "skin"), p!(-1, -3, 55, 130, 45, "shirt"), p!(1, -3, 55, 130, 45, "shirt"), + p!(0, -3, 65, 140, 50, "shirt"), p!(-2, -3, 110, 180, 85, "hand"), p!(2, -3, 110, 180, 85, "hand"), p!(-3, -3, 100, 170, 75, "hand"), p!(3, -3, 100, 170, 75, "hand"), - // Torso: rags 3x2 p!(-1, -2, 55, 130, 45, "shirt"), p!(0, -2, 65, 140, 50, "shirt"), p!(1, -2, 55, 130, 45, "shirt"), + p!(-2, -2, 110, 180, 85, "hand"), + p!(2, -2, 110, 180, 85, "hand"), p!(-1, -1, 50, 120, 40, "shirt"), p!(0, -1, 60, 130, 45, "shirt"), p!(1, -1, 50, 120, 40, "shirt"), - // Arms lower - p!(-2, -2, 110, 180, 85, "hand"), - p!(2, -2, 110, 180, 85, "hand"), p!(-2, -1, 100, 170, 75, "hand"), p!(2, -1, 100, 170, 75, "hand"), - // Loincloth p!(-1, 0, 40, 90, 30, "pants"), p!(0, 0, 45, 100, 32, "pants"), p!(1, 0, 40, 90, 30, "pants"), - // Legs p!(-1, 1, 40, 100, 30, "pants"), p!(0, 1, 40, 100, 30, "pants"), p!(1, 1, 40, 100, 30, "pants"), @@ -152,7 +165,6 @@ impl BodyTemplate { p!(1, 2, 35, 85, 25, "pants"), p!(-2, 2, 35, 85, 25, "pants"), p!(2, 2, 35, 85, 25, "pants"), - // Bare feet p!(-2, 3, 110, 180, 85, "skin"), p!(0, 3, 110, 180, 85, "skin"), p!(2, 3, 110, 180, 85, "skin"), @@ -340,6 +352,11 @@ impl BodyTemplate { ("tail", '.'), ("hat", '^'), ("belt", '='), + ("eye", ':'), + ("hair", '~'), + ("cape", '>'), + ("tooth", '.'), + ("ear", '<'), ]); for part in &self.parts { diff --git a/src/entity/player.rs b/src/entity/player.rs index 11cb43f..a224262 100644 --- a/src/entity/player.rs +++ b/src/entity/player.rs @@ -8,6 +8,7 @@ pub struct Player { pub inventory: Vec, pub weapon: Option, pub armor: Option, + pub facing_right: bool, } impl Player { @@ -20,6 +21,7 @@ impl Player { inventory: Vec::new(), weapon: None, armor: None, + facing_right: true, } } diff --git a/src/main.rs b/src/main.rs index d4d5db2..c36707a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -229,6 +229,12 @@ fn run_gpu_mode(title: &str) { } => { input.on_key_event(key_event.physical_key, key_event.state); } + WindowEvent::CursorMoved { position, .. } => { + input.on_mouse_move(position.x, position.y); + } + WindowEvent::MouseInput { state, button, .. } => { + input.on_mouse_button(button, state); + } WindowEvent::Focused(false) => { input.clear_keys(); } @@ -276,14 +282,36 @@ fn run_gpu_mode(title: &str) { game.player.stop_horizontal(&mut game.entities); } - if input.shoot_left { - game.player_shoot(-1.0, 0.0); - } else if input.shoot_right { - game.player_shoot(1.0, 0.0); - } else if input.shoot_up { - game.player_shoot(0.0, -1.0); - } else if input.shoot_down { - game.player_shoot(0.0, 1.0); + if input.shoot_mouse + || input.shoot_left + || input.shoot_right + || input.shoot_up + || input.shoot_down + { + let (px, py) = game.player.center(&game.entities); + let player_screen_x = ((px as i32 - game.cam_x) as f64) * 8.0 + 4.0; + let player_screen_y = ((py as i32 - game.cam_y) as f64) * 8.0 + 4.0; + + let (dx, dy) = if input.shoot_mouse { + let mx = input.mouse_x - player_screen_x; + let my = input.mouse_y - player_screen_y; + let len = (mx * mx + my * my).sqrt(); + if len < 1.0 { + (1.0, 0.0) + } else { + (mx / len, my / len) + } + } else if input.shoot_left { + (-1.0, 0.0) + } else if input.shoot_right { + (1.0, 0.0) + } else if input.shoot_up { + (0.0, -1.0) + } else { + (0.0, 1.0) + }; + + game.player_shoot(dx as f32, dy as f32); } if input.toggle_fireball { game.fireball_mode = !game.fireball_mode; @@ -298,6 +326,12 @@ fn run_gpu_mode(title: &str) { game.drop_item(0); } + { + let (px, _py) = game.player.center(&game.entities); + let player_screen_x = ((px as i32 - game.cam_x) as f64) * 8.0 + 4.0; + game.player.facing_right = input.mouse_x >= player_screen_x; + } + if input.cam_left { game.cam_offset_x -= 3; } diff --git a/src/render/window_input.rs b/src/render/window_input.rs index 9a9d659..f8c749c 100644 --- a/src/render/window_input.rs +++ b/src/render/window_input.rs @@ -19,6 +19,11 @@ pub struct WindowInput { pub cam_down: bool, pub quit: bool, pub paint: Option, + pub mouse_x: f64, + pub mouse_y: f64, + pub mouse_left: bool, + pub mouse_left_was_down: bool, + pub shoot_mouse: bool, jump_was_down: bool, shoot_left_was_down: bool, shoot_right_was_down: bool, @@ -51,6 +56,11 @@ impl WindowInput { cam_down: false, quit: false, paint: None, + mouse_x: 0.0, + mouse_y: 0.0, + mouse_left: false, + mouse_left_was_down: false, + shoot_mouse: false, jump_was_down: false, shoot_left_was_down: false, shoot_right_was_down: false, @@ -82,6 +92,28 @@ impl WindowInput { pub fn clear_keys(&mut self) { self.down_keys.clear(); + self.mouse_left = false; + } + + pub fn on_mouse_move(&mut self, x: f64, y: f64) { + self.mouse_x = x; + self.mouse_y = y; + } + + pub fn on_mouse_button( + &mut self, + button: winit::event::MouseButton, + state: winit::event::ElementState, + ) { + match (button, state) { + (winit::event::MouseButton::Left, winit::event::ElementState::Pressed) => { + self.mouse_left = true; + } + (winit::event::MouseButton::Left, winit::event::ElementState::Released) => { + self.mouse_left = false; + } + _ => {} + } } pub fn update(&mut self) { @@ -129,6 +161,9 @@ impl WindowInput { self.drop_item = now_drop_item && !self.drop_item_was_down; self.drop_item_was_down = now_drop_item; + self.shoot_mouse = self.mouse_left && !self.mouse_left_was_down; + self.mouse_left_was_down = self.mouse_left; + self.cam_left = keys.contains(&KeyCode::KeyY); self.cam_right = keys.contains(&KeyCode::KeyU); self.cam_up = keys.contains(&KeyCode::KeyI);