diff --git a/assets/Whale/Flippers(45x36).png b/assets/Whale/Flippers(45x36).png new file mode 100644 index 0000000000..4fd42ea84d Binary files /dev/null and b/assets/Whale/Flippers(45x36).png differ diff --git a/assets/Whale/Flippers(65x45).png b/assets/Whale/Flippers(65x45).png new file mode 100644 index 0000000000..1994f1e448 Binary files /dev/null and b/assets/Whale/Flippers(65x45).png differ diff --git a/assets/levels/test_level.json b/assets/levels/test_level.json index ac9920fb0f..2b12d42591 100644 --- a/assets/levels/test_level.json +++ b/assets/levels/test_level.json @@ -298,7 +298,19 @@ "width":0, "x":435.333333333333, "y":547.333333333334 - }], + }, + { + "height":0, + "id":152, + "name":"flippers", + "point":true, + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":500, + "y":650 + }], "opacity":1, "type":"objectgroup", "visible":true, diff --git a/src/items.rs b/src/items.rs index e682f6b2f2..1b8be293f3 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2,6 +2,7 @@ //! Proto-mods, eventually some of the items will move to some sort of a wasm runtime mod cannon; +pub mod flippers; mod galleon; mod grenades; mod gun; @@ -132,6 +133,18 @@ pub const ITEMS: &[Item] = &[ fxses: &[], network_ready: false, }, + Item { + tiled_name: "flippers", + constructor: flippers::Flippers::spawn, + tiled_offset: (0., 0.), + textures: &[ + ("flippers_item", "assets/Whale/Flippers(65x45).png"), + ("flippers_weared", "assets/Whale/Flippers(45x36).png"), + ], + sounds: &[], + fxses: &[], + network_ready: false, + }, Item { tiled_name: "grenades", constructor: grenades::Grenades::spawn, diff --git a/src/items/flippers.rs b/src/items/flippers.rs new file mode 100644 index 0000000000..594548d754 --- /dev/null +++ b/src/items/flippers.rs @@ -0,0 +1,74 @@ +use macroquad::{ + color, + experimental::{ + animation::{AnimatedSprite, Animation}, + collections::storage, + scene::{HandleUntyped, Node, RefMut}, + }, + prelude::*, +}; + +use crate::{nodes::Player, Resources}; + +pub struct Flippers { + sprite: AnimatedSprite, + pos: Vec2, +} + +impl Flippers { + pub fn spawn(pos: Vec2) -> HandleUntyped { + let sprite = AnimatedSprite::new( + 65, + 45, + &[Animation { + name: "idle".to_string(), + row: 0, + frames: 1, + fps: 1, + }], + false, + ); + + scene::add_node(Flippers { sprite, pos }).untyped() + } +} + +impl Node for Flippers { + fn fixed_update(node: scene::RefMut) { + let flippers_rect = Rect::new(node.pos.x, node.pos.y, 30.0, 30.0); + let mut picked_up = false; + + for mut player in scene::find_nodes_by_type::() { + let player_rect = Rect::new(player.body.pos.x, player.body.pos.y, 30.0, 54.0); + if player_rect.overlaps(&flippers_rect) && !player.can_head_boink { + player.can_extra_jump = true; + player.can_head_boink = true; + picked_up = true; + } + } + + if picked_up { + node.delete(); + } + } + + fn draw(mut node: RefMut) { + node.sprite.update(); + + let resources = storage::get_mut::(); + + draw_texture_ex( + resources.items_textures["flippers/flippers_item"], + node.pos.x, + node.pos.y, + color::WHITE, + DrawTextureParams { + source: Some(node.sprite.frame().source_rect), + dest_size: Some(node.sprite.frame().dest_size), + flip_x: false, + rotation: 0.0, + ..Default::default() + }, + ) + } +} diff --git a/src/items/shoes.rs b/src/items/shoes.rs index 1fd29fcd93..97e0b0aa83 100644 --- a/src/items/shoes.rs +++ b/src/items/shoes.rs @@ -103,7 +103,7 @@ impl Node for Shoes { Self::COLLIDER_HEIGHT, ); for mut player in scene::find_nodes_by_type::() { - if player.get_hitbox().overlaps(&shoes_rect) { + if player.get_hitbox().overlaps(&shoes_rect) && !player.can_extra_jump { player.can_head_boink = true; node.delete(); return; diff --git a/src/nodes/player.rs b/src/nodes/player.rs index 24eb2478e1..258ca45747 100644 --- a/src/nodes/player.rs +++ b/src/nodes/player.rs @@ -14,6 +14,7 @@ use macroquad::{ use crate::{ capabilities::{NetworkReplicate, PhysicsObject, Weapon, WeaponTrait}, components::PhysicsBody, + items::flippers::Flippers, items::shoes::Shoes, GameWorld, Input, Resources, }; @@ -90,6 +91,9 @@ pub struct Player { pub incapacitated_duration: f32, pub incapacitated_timer: f32, + pub can_extra_jump: bool, + pub extra_jump_count: i32, + pub back_armor: i32, pub can_head_boink: bool, } @@ -114,6 +118,7 @@ impl Player { pub const JUMP_GRACE_TIME: f32 = 0.15; pub const PICK_GRACE_TIME: f32 = 0.30; pub const FLOAT_SPEED: f32 = 100.0; + pub const MAX_JUMP_COUNT: i32 = 3; pub const INCAPACITATED_BREAK_FACTOR: f32 = 0.9; pub const INCAPACITATED_STOP_THRESHOLD: f32 = 20.0; @@ -245,6 +250,8 @@ impl Player { ai: Some(ai::Ai::new()), camera_box: Rect::new(spawner_pos.x - 30., spawner_pos.y - 150., 100., 210.), can_head_boink: false, + can_extra_jump: false, + extra_jump_count: 0, back_armor: 0, is_crouched: false, incapacitated_timer: 0.0, @@ -360,7 +367,11 @@ impl Player { let mut this = scene::get_node(handle); - if this.can_head_boink { + if this.can_extra_jump { + Flippers::spawn(this.body.pos); + this.can_extra_jump = false; + this.can_head_boink = false; + } else if this.can_head_boink { Shoes::spawn(this.body.pos); this.can_head_boink = false; } @@ -513,10 +524,11 @@ impl Player { } // if in jump and want to jump again - if !node.body.on_ground - && node.input.jump - && !node.last_frame_input.jump + if (node.extra_jump_count >= Self::MAX_JUMP_COUNT || !node.can_extra_jump) && node.jump_grace_timer <= 0.0 + && !node.last_frame_input.jump + && node.input.jump + && !node.body.on_ground { // if !node.was_floating { @@ -553,14 +565,18 @@ impl Player { node.body.descent(); } - if !node.input.down - && node.input.jump - && !node.last_frame_input.jump - && node.jump_grace_timer > 0. - { - node.jump_grace_timer = 0.0; - - node.jump(); + if !node.input.down && node.input.jump && !node.last_frame_input.jump { + if node.can_extra_jump + && node.extra_jump_count < Self::MAX_JUMP_COUNT + && node.jump_grace_timer <= 0. + { + node.jump(); + node.extra_jump_count += 1; + node.jump_grace_timer = Self::JUMP_GRACE_TIME; + } else if node.jump_grace_timer > 0. && node.extra_jump_count == 0 { + node.jump_grace_timer = 0.0; + node.jump(); + } } if node.weapon.is_none() && node.pick_grace_timer > 0. { @@ -708,6 +724,7 @@ impl Player { if node.body.on_ground && !node.input.jump { node.jump_grace_timer = Self::JUMP_GRACE_TIME; + node.extra_jump_count = 0; } else if node.jump_grace_timer > 0. { node.jump_grace_timer -= get_frame_time(); } @@ -722,8 +739,19 @@ impl Player { let is_overlapping = hitbox.overlaps(&other_hitbox); if is_overlapping && hitbox.y + 60.0 < other_hitbox.y + Self::HEAD_THRESHOLD { let resources = storage::get::(); - play_sound_once(resources.jump_sound); - other.kill(!node.body.facing); + if node.can_extra_jump { + if other.weapon.is_some() { + other.drop_weapon(); + play_sound_once(resources.player_throw_sound); + node.jump(); + } else if other.back_armor > 0 { + other.back_armor -= 1; + node.jump(); + } + } else if !node.can_extra_jump { + other.kill(!node.body.facing); + play_sound_once(resources.jump_sound); + } } } } @@ -761,14 +789,14 @@ impl scene::Node for Player { draw_texture_ex( if node.controller_id == 0 { - if node.can_head_boink { + if node.can_head_boink && !node.can_extra_jump { resources.whale_boots_blue } else { resources.whale_blue } } else { // - if node.can_head_boink { + if node.can_head_boink && !node.can_extra_jump { resources.whale_boots_green } else { resources.whale_green @@ -786,6 +814,20 @@ impl scene::Node for Player { }, ); + // draw flippers + if node.can_extra_jump { + draw_texture_ex( + resources.items_textures["flippers/flippers_weared"], + node.body.pos.x + if node.body.facing { 5. } else { -24. }, + node.body.pos.y + 30., + color::WHITE, + DrawTextureParams { + flip_x: !node.body.facing, + ..Default::default() + }, + ) + } + // draw turtle shell on player if the player has back armor if node.back_armor > 0 { draw_texture_ex(