diff --git a/assets/items.json b/assets/items.json index 7124c289db..af30657a06 100644 --- a/assets/items.json +++ b/assets/items.json @@ -5,6 +5,8 @@ "items/grenades.json", "items/machine_gun.json", "items/mines.json", + "items/starfish.json", + "items/starfish_launcher.json", "items/musket.json", "items/sniper_rifle.json", "items/sword.json", diff --git a/assets/items/starfish.json b/assets/items/starfish.json new file mode 100644 index 0000000000..734061d143 --- /dev/null +++ b/assets/items/starfish.json @@ -0,0 +1,105 @@ +{ + "id": "starfish", + "name": "Starfish", + "type": "weapon", + "can_rotate": true, + "cooldown": 0.5, + "uses": 1, + "deplete_behavior": "destroy", + "effects": [ + { + "type": "triggered_effect", + "trigger": ["player", "enemy", "explosion", "projectile"], + "expire_time": 3.0, + "expire_effects": [ + { + "type": "spawn_item", + "item": "starfish", + "offset": { + "x": 0, + "y": -10 + }, + "inherit_spawner_velocity": true + } + ], + "can_rotate": true, + "angular_velocity": -620, + "velocity": { + "x": 12.0, + "y": 0.0 + }, + "gravity": 0.01, + "bouncyness": 1.0, + "size": { + "x": 19, + "y": 18 + }, + "activation_delay": 0.1, + "grab_options": { + "zone_size": { + "x": 60, + "y": 18 + }, + "zone_offset": { + "x": 0, + "y": -9 + }, + "equips_item": "starfish" + }, + "effects": [ + { + "type": "circle_collider", + "radius": 25, + "is_explosion": true, + "sound_effect": "bullet_hit_dull" + }, + { + "type": "spawn_item", + "item": "starfish", + "offset": { + "x": 20, + "y": -10 + }, + "inherit_spawner_velocity": true + } + ], + "collide_with_platforms": true, + "sprite": { + "texture": "starfish", + "autoplay_id": "effect", + "animations": [ + { + "id": "effect", + "row": 0, + "frames": 1, + "fps": 1 + } + ] + } + } + ], + "collider_size": { + "x": 19, + "y": 18 + }, + "mount_offset": { + "x": -25, + "y": 5 + }, + "effect_offset": { + "x": 10, + "y": 9 + }, + "sprite": { + "texture": "starfish", + "autoplay_id": "idle", + "animations": [ + { + "id": "idle", + "row": 0, + "frames": 1, + "fps": 1 + } + ] + } +} diff --git a/assets/items/starfish_launcher.json b/assets/items/starfish_launcher.json new file mode 100644 index 0000000000..f3b9a21e2f --- /dev/null +++ b/assets/items/starfish_launcher.json @@ -0,0 +1,84 @@ +{ + "id": "starfish_launcher", + "name": "Starfish Launcher", + "type": "weapon", + "cooldown": 0.75, + "uses": 4, + "effects": [ + { + "type": "triggered_effect", + "trigger": ["player", "enemy", "explosion", "projectile"], + "expire_time": 3.0, + "can_rotate": true, + "angular_velocity": -620, + "velocity": { + "x": 12.0, + "y": 0.0 + }, + "spread": 30, + "gravity": 0.01, + "bouncyness": 1.0, + "size": { + "x": 19, + "y": 18 + }, + "activation_delay": 0.1, + "grab_options": { + "zone_size": { + "x": 50, + "y": 18 + }, + "zone_offset": { + "x": 0, + "y": -9 + }, + "equips_item": "starfish" + }, + "effects": [ + { + "type": "circle_collider", + "radius": 25, + "is_explosion": true, + "sound_effect": "bullet_hit_dull" + } + ], + "collide_with_platforms": true, + "sprite": { + "texture": "starfish", + "autoplay_id": "effect", + "animations": [ + { + "id": "effect", + "row": 0, + "frames": 1, + "fps": 1 + } + ] + } + } + ], + "collider_size": { + "x": 37, + "y": 20 + }, + "mount_offset": { + "x": -17, + "y": -5 + }, + "effect_offset": { + "x": 10, + "y": 9 + }, + "sprite": { + "texture": "starfish_launcher", + "autoplay_id": "idle", + "animations": [ + { + "id": "idle", + "row": 0, + "frames": 1, + "fps": 1 + } + ] + } +} diff --git a/assets/maps/test_level.json b/assets/maps/test_level.json index e8247d663e..7421cbc746 100644 --- a/assets/maps/test_level.json +++ b/assets/maps/test_level.json @@ -809,6 +809,14 @@ "kind": "object_layer", "has_collision": false, "objects": [ + { + "id": "starfish_launcher", + "kind": "item", + "position": { + "x": 782.8819, + "y": 675.5 + } + }, { "id": "fish_school", "kind": "environment", @@ -1055,22 +1063,22 @@ ], "tilesets": [ { - "id": "decorations1", - "texture_id": "default_decoration", + "id": "tileset", + "texture_id": "default_tileset", "texture_size": { - "x": 240, - "y": 102 + "x": 224, + "y": 288 }, "tile_size": { - "x": 48.0, - "y": 51.0 + "x": 32.0, + "y": 32.0 }, "grid_size": { - "x": 5, - "y": 2 + "x": 7, + "y": 9 }, - "first_tile_id": 64, - "tile_cnt": 10, + "first_tile_id": 1, + "tile_cnt": 63, "tile_subdivisions": { "x": 3, "y": 3 @@ -1165,31 +1173,6 @@ false, false, false, - false - ] - }, - { - "id": "tileset", - "texture_id": "default_tileset", - "texture_size": { - "x": 224, - "y": 288 - }, - "tile_size": { - "x": 32.0, - "y": 32.0 - }, - "grid_size": { - "x": 7, - "y": 9 - }, - "first_tile_id": 1, - "tile_cnt": 63, - "tile_subdivisions": { - "x": 3, - "y": 3 - }, - "autotile_mask": [ false, false, false, @@ -1667,6 +1650,42 @@ false, false, false, + false + ], + "tile_attributes": { + "56": [ + "jumpthrough" + ], + "60": [ + "jumpthrough" + ], + "58": [ + "jumpthrough" + ] + } + }, + { + "id": "decorations1", + "texture_id": "default_decoration", + "texture_size": { + "x": 240, + "y": 102 + }, + "tile_size": { + "x": 48.0, + "y": 51.0 + }, + "grid_size": { + "x": 5, + "y": 2 + }, + "first_tile_id": 64, + "tile_cnt": 10, + "tile_subdivisions": { + "x": 3, + "y": 3 + }, + "autotile_mask": [ false, false, false, @@ -1757,18 +1776,7 @@ false, false, false - ], - "tile_attributes": { - "58": [ - "jumpthrough" - ], - "60": [ - "jumpthrough" - ], - "56": [ - "jumpthrough" - ] - } + ] } ], "spawn_points": [ diff --git a/assets/sounds.json b/assets/sounds.json index 51cb9685c9..a5b4460469 100644 --- a/assets/sounds.json +++ b/assets/sounds.json @@ -38,5 +38,9 @@ { "id": "explode", "path": "sounds/explode_m.wav" + }, + { + "id": "bullet_hit_dull", + "path": "sounds/bullet_hit_dull.wav" } ] \ No newline at end of file diff --git a/assets/textures.json b/assets/textures.json index b84220fbc8..c033dbe117 100644 --- a/assets/textures.json +++ b/assets/textures.json @@ -517,6 +517,24 @@ "y": 9 } }, + { + "id": "starfish", + "path": "textures/items/StarFish(19x18).png", + "type": "spritesheet", + "sprite_size": { + "x": 19, + "y": 18 + } + }, + { + "id": "starfish_launcher", + "path": "textures/items/StarfishLauncher(37x20).png", + "type": "spritesheet", + "sprite_size": { + "x": 37, + "y": 20 + } + }, { "id": "fish_school_icon", "path": "textures/items/FishSchoolIcon(64x64).png", diff --git a/assets/textures/items/StarFish(19x18).png b/assets/textures/items/StarFish(19x18).png new file mode 100644 index 0000000000..1f5d54febd Binary files /dev/null and b/assets/textures/items/StarFish(19x18).png differ diff --git a/assets/textures/items/StarfishLauncher(37x20).png b/assets/textures/items/StarfishLauncher(37x20).png new file mode 100644 index 0000000000..d78a440b84 Binary files /dev/null and b/assets/textures/items/StarfishLauncher(37x20).png differ diff --git a/src/effects/active/mod.rs b/src/effects/active/mod.rs index b80d691fe5..4309cadee1 100644 --- a/src/effects/active/mod.rs +++ b/src/effects/active/mod.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use core::math::{deg_to_rad, rotate_vector, IsZero}; use core::Result; +use crate::items::spawn_item; use crate::Resources; use crate::{PassiveEffectInstance, PassiveEffectMetadata}; @@ -42,6 +43,7 @@ struct RectCollider { pub fn spawn_active_effect( world: &mut World, owner: Entity, + spawner: Entity, origin: Vec2, params: ActiveEffectMetadata, ) -> Result<()> { @@ -186,6 +188,34 @@ pub fn spawn_active_effect( }, ); } + ActiveEffectKind::SpawnItem { + item, + offset, + inherit_spawner_velocity, + } => { + let resources = storage::get::(); + let item_meta = resources.items.get(&item).expect("Item doesn't exist"); + + match spawn_item(world, origin + offset, item_meta.clone()) { + Ok(entity) => { + if inherit_spawner_velocity { + let spawner_velocity = { + let mut spawner_body_query = + world.query_one::<&PhysicsBody>(spawner).unwrap(); + let spawner_body = spawner_body_query.get().unwrap(); + spawner_body.velocity + }; + + let mut entity_body = + world.query_one_mut::<&mut PhysicsBody>(entity).unwrap(); + entity_body.velocity = spawner_velocity; + } + } + Err(e) => { + println!("WARNING: {:?}", e); + } + } + } } for (damage_from_entity, damage_to_entity) in damage.drain(0..) { @@ -282,6 +312,13 @@ pub enum ActiveEffectKind { #[serde(default, skip_serializing_if = "Vec::is_empty")] particles: Vec, }, + SpawnItem { + item: String, + #[serde(default, with = "core::json::vec2_def")] + offset: Vec2, + #[serde(default)] + inherit_spawner_velocity: bool, + }, } pub fn debug_draw_active_effects(world: &mut World) { @@ -322,6 +359,15 @@ pub fn debug_draw_active_effects(world: &mut World) { } } + for (_, (transform, body, effect)) in + world.query_mut::<(&Transform, &PhysicsBody, &TriggeredEffect)>() + { + if let Some(opts) = &effect.grab_options { + let rect = opts.get_collider_rect(transform.position, body.velocity); + draw_rectangle_lines(rect.x, rect.y, rect.w, rect.h, 2.0, color::ORANGE); + } + } + for e in to_remove.drain(0..) { world.despawn(e).unwrap(); } diff --git a/src/effects/active/triggered.rs b/src/effects/active/triggered.rs index bd83ed8360..8a7b636ff4 100644 --- a/src/effects/active/triggered.rs +++ b/src/effects/active/triggered.rs @@ -5,13 +5,14 @@ use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; -use core::math::deg_to_rad; +use core::math::{deg_to_rad, rotate_vector}; use core::{Result, Transform}; use crate::effects::active::spawn_active_effect; +use crate::items::spawn_item; use crate::particles::{ParticleEmitter, ParticleEmitterMetadata}; -use crate::physics; -use crate::player::{Player, PlayerState}; +use crate::player::{Player, PlayerController, PlayerInventory, PlayerState}; +use crate::{physics, Resources}; use crate::{ActiveEffectMetadata, AnimatedSpriteMetadata, CollisionWorld, PhysicsBody}; use crate::{Drawable, DrawableKind, PhysicsBodyParams}; @@ -38,6 +39,8 @@ pub struct TriggeredEffect { pub trigger: Vec, pub effects: Vec, pub activation_delay: f32, + pub expire_time: Option, + pub expire_effects: Vec, pub trigger_delay: f32, pub timed_trigger: Option, pub is_kickable: bool, @@ -48,6 +51,7 @@ pub struct TriggeredEffect { pub is_triggered: bool, /// This holds a handle to the player that triggered the effect, if applicable. pub triggered_by: Option, + pub grab_options: Option, pub kick_delay_timer: f32, pub activation_timer: f32, pub trigger_delay_timer: f32, @@ -61,6 +65,8 @@ impl TriggeredEffect { trigger: meta.trigger, effects: meta.effects, activation_delay: meta.activation_delay, + expire_time: meta.expire_time, + expire_effects: meta.expire_effects, trigger_delay: meta.trigger_delay, timed_trigger: meta.timed_trigger, is_kickable: meta.is_kickable, @@ -68,6 +74,7 @@ impl TriggeredEffect { should_collide_with_platforms: meta.should_collide_with_platforms, is_triggered: false, triggered_by: None, + grab_options: meta.grab_options, kick_delay_timer: 0.0, activation_timer: 0.0, trigger_delay_timer: 0.0, @@ -88,6 +95,13 @@ pub fn spawn_triggered_effect( velocity.x = -velocity.x; } + if meta.spread != 0.0 { + let rad = deg_to_rad(meta.spread); + let spread = rand::gen_range(-rad, rad); + + velocity = rotate_vector(velocity, spread); + } + let offset = -meta.size / 2.0; let actor = { @@ -108,6 +122,8 @@ pub fn spawn_triggered_effect( size: meta.size, can_rotate: meta.can_rotate, gravity: meta.gravity, + angular_velocity: meta.angular_velocity, + bouncyness: meta.bouncyness, ..Default::default() }, ), @@ -254,12 +270,23 @@ pub fn fixed_update_triggered_effects(world: &mut World) { effect.effects.clone(), ); to_trigger.push(params); + } else if let Some(lifetime) = effect.expire_time { + if effect.activation_timer >= lifetime { + let params = ( + entity, + effect.triggered_by, + effect.owner, + transform.position, + effect.expire_effects.clone(), + ); + to_trigger.push(params); + } } } for (e, _, owner, origin, effects) in to_trigger.drain(0..) { for params in effects { - if let Err(err) = spawn_active_effect(world, owner, origin, params) { + if let Err(err) = spawn_active_effect(world, owner, e, origin, params) { #[cfg(debug_assertions)] println!("WARNING: {}", err); } @@ -272,6 +299,85 @@ pub fn fixed_update_triggered_effects(world: &mut World) { } } +pub fn update_triggered_effects(world: &mut World) { + let mut to_grab = Vec::new(); + + let players = world + .query::<( + &Player, + &Transform, + &PhysicsBody, + &PlayerController, + &PlayerInventory, + )>() + .iter() + .filter_map(|(e, (player, transform, body, controller, inventory))| { + if player.state == PlayerState::Dead { + None + } else { + Some(( + e, + player.is_facing_left, + transform.position, + body.size, + controller.clone(), + inventory.weapon.is_some(), + )) + } + }) + .collect::>(); + + 'effects: for (entity, (effect, body, transform)) in world + .query::<(&mut TriggeredEffect, &PhysicsBody, &Transform)>() + .iter() + { + if let Some(opts) = &effect.grab_options { + let collider = opts.get_collider_rect(transform.position, body.velocity); + + for (pe, player_is_facing_left, player_pos, size, controller, has_weapon) in &players { + let is_on_left = transform.position.x < player_pos.x; + // Players with weapons cannot grab items + if *has_weapon || (is_on_left != *player_is_facing_left && opts.must_be_facing) { + continue; + } + let player_collider = Rect::new(player_pos.x, player_pos.y, size.x, size.y); + + if collider.overlaps(&player_collider) && controller.should_pickup { + if let Some(item_id) = &opts.equips_item { + to_grab.push((entity, pe, item_id.clone())); + } + continue 'effects; + } + } + } + } + + for (effect_entity, player_entity, item_id) in to_grab { + let resources = storage::get::(); + let item = spawn_item( + world, + Vec2::default(), + resources + .items + .get(&item_id) + .expect("Item not found") + .clone(), + ) + .unwrap(); + + let player_inventory = world + .query_one_mut::<&mut PlayerInventory>(*player_entity) + .unwrap(); + + player_inventory.pending_weapon_replacement = Some(item); + + if let Err(err) = world.despawn(effect_entity) { + #[cfg(debug_assertions)] + println!("WARNING: {}", err); + } + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct TriggeredEffectMetadata { /// The effects to instantiate when the triggers condition is met @@ -283,15 +389,24 @@ pub struct TriggeredEffectMetadata { /// This specifies the size of the trigger. #[serde(with = "core::json::vec2_def")] pub size: Vec2, + #[serde(default)] + pub grab_options: Option, /// This specifies the valid trigger conditions for the trigger. #[serde(default)] pub trigger: Vec, /// This specifies the velocity of the triggers body when it is instantiated. #[serde(default, with = "core::json::vec2_def")] pub velocity: Vec2, + /// The number of degrees to randomly vary the velocity angle either up or down when deploying + /// the effect + #[serde(default)] + pub spread: f32, /// This specifies the initial rotation of the sprite. #[serde(default)] pub rotation: f32, + /// This specifies the speed at which the object should rotate in mid-air, specified in degrees per second + #[serde(default)] + pub angular_velocity: f32, /// This can be used to add an animated sprite to the trigger. If only a sprite is desired, an /// animation with only one frame can be used. #[serde(default, alias = "animation", skip_serializing_if = "Option::is_none")] @@ -312,6 +427,12 @@ pub struct TriggeredEffectMetadata { /// and trigger the effect immediately. #[serde(default, skip_serializing_if = "Option::is_none")] pub timed_trigger: Option, + /// An optional lifetime after which the effect with despawn, _without_ triggering + #[serde(default)] + pub expire_time: Option, + /// A list of effects that will activate when the item expires, if `expire_time` is not null + #[serde(default)] + pub expire_effects: Vec, /// If this is `true` the trigger is kicked by a player, if it hits him while he is facing it #[serde(default)] pub is_kickable: bool, @@ -324,6 +445,8 @@ pub struct TriggeredEffectMetadata { pub can_rotate: bool, #[serde(default = "default_physics_gravity")] pub gravity: f32, + #[serde(default)] + pub bouncyness: f32, } impl Default for TriggeredEffectMetadata { @@ -332,21 +455,72 @@ impl Default for TriggeredEffectMetadata { effects: Vec::new(), particles: Vec::new(), size: vec2(6.0, 6.0), + grab_options: None, trigger: Vec::new(), velocity: Vec2::ZERO, + spread: 0.0, rotation: 0.0, + angular_velocity: 0.0, sprite: None, activation_delay: 0.0, trigger_delay: 0.0, timed_trigger: None, + expire_time: None, + expire_effects: Vec::new(), is_kickable: false, should_collide_with_platforms: false, can_rotate: false, gravity: default_physics_gravity(), + bouncyness: 0.0, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct TriggeredEffectGrabOptions { + /// The size of the grab-zone + #[serde(with = "core::json::vec2_def")] + pub zone_size: Vec2, + /// The offset of the grab-zone + #[serde(default, with = "core::json::vec2_def")] + pub zone_offset: Vec2, + /// Whether or not the player must be facing the item to grab it + #[serde(default = "default_true")] + pub must_be_facing: bool, + /// The item that is equipped to the player when this effect is grabbed + #[serde(default)] + pub equips_item: Option, +} + +impl TriggeredEffectGrabOptions { + pub fn get_collider_rect(&self, pos: Vec2, velocity: Vec2) -> Rect { + let offset = self.zone_offset; + let width = self.zone_size.x; + let height = self.zone_size.y; + let flip_x = velocity.x < 0.0; + let flip_y = velocity.y < 0.0; + Rect { + // The velocity is used to flip the collision box in the direction the item is moving + x: if flip_x { + pos.x - width - offset.x + } else { + pos.x + offset.x + }, + y: if flip_y { + pos.y - height - offset.y + } else { + pos.y + offset.y + }, + w: width, + h: height, } } } +fn default_true() -> bool { + true +} + fn default_physics_gravity() -> f32 { physics::GRAVITY } diff --git a/src/game/mod.rs b/src/game/mod.rs index 6a18bc569b..2c220642f6 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -32,7 +32,7 @@ use crate::{ use crate::effects::active::debug_draw_active_effects; use crate::effects::active::projectiles::fixed_update_projectiles; -use crate::effects::active::triggered::fixed_update_triggered_effects; +use crate::effects::active::triggered::{fixed_update_triggered_effects, update_triggered_effects}; use crate::items::spawn_item; use crate::map::{ debug_draw_fish_schools, fixed_update_sproingers, spawn_crab, spawn_decoration, @@ -122,6 +122,7 @@ impl Game { .add_system(update_player_inventory) .add_system(update_player_passive_effects) .add_system(update_player_events) + .add_system(update_triggered_effects) .add_system(update_fish_schools) .add_system(update_crabs); diff --git a/src/items.rs b/src/items.rs index a2af8e2987..4e1a2ecf2a 100644 --- a/src/items.rs +++ b/src/items.rs @@ -142,6 +142,8 @@ pub struct MapItemMetadata { pub name: String, #[serde(flatten)] pub kind: MapItemKind, + #[serde(default)] + pub can_rotate: bool, #[serde(with = "core::json::vec2_def")] pub collider_size: Vec2, #[serde(default, with = "core::json::vec2_def")] @@ -194,7 +196,7 @@ pub fn spawn_item(world: &mut World, position: Vec2, meta: MapItemMetadata) -> R offset: collider_offset, has_mass: true, has_friction: true, - can_rotate: false, + can_rotate: meta.can_rotate, ..Default::default() }, ), @@ -443,7 +445,7 @@ pub fn fire_weapon(world: &mut World, entity: Entity, owner: Entity) -> Result<( } for params in effects { - spawn_active_effect(world, owner, origin, params)?; + spawn_active_effect(world, owner, entity, origin, params)?; } Ok(()) diff --git a/src/physics.rs b/src/physics.rs index a9f0c0e47e..12bb7475fc 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -9,7 +9,10 @@ use serde::{Deserialize, Serialize}; use hecs::World; use crate::{CollisionWorld, Map}; -use core::Transform; +use core::{ + math::{deg_to_rad, IsZero}, + Transform, +}; pub const GRAVITY: f32 = 2.5; pub const TERMINAL_VELOCITY: f32 = 10.0; @@ -61,6 +64,7 @@ pub struct PhysicsBodyParams { pub has_mass: bool, pub has_friction: bool, pub can_rotate: bool, + pub angular_velocity: f32, pub bouncyness: f32, pub gravity: f32, } @@ -73,6 +77,7 @@ impl Default for PhysicsBodyParams { has_mass: true, has_friction: true, can_rotate: true, + angular_velocity: 0.0, bouncyness: 0.0, gravity: GRAVITY, } @@ -87,6 +92,8 @@ pub struct PhysicsBody { pub offset: Vec2, pub size: Vec2, pub velocity: Vec2, + /// Angular velocity in degrees per second + pub angular_velocity: f32, pub is_on_ground: bool, pub was_on_ground: bool, /// Will be `true` if the body is currently on top of a platform/jumpthrough tile @@ -113,6 +120,7 @@ impl PhysicsBody { offset: params.offset, size: params.size, velocity, + angular_velocity: params.angular_velocity, is_on_ground: false, was_on_ground: false, is_on_platform: false, @@ -174,7 +182,12 @@ pub fn fixed_update_physics_bodies(world: &mut World) { } if body.can_rotate { - apply_rotation(transform, &mut body.velocity, body.is_on_ground); + apply_rotation( + transform, + &mut body.velocity, + body.angular_velocity, + body.is_on_ground, + ); } if body.is_on_ground && body.has_friction { @@ -260,7 +273,7 @@ pub fn fixed_update_rigid_bodies(world: &mut World) { transform.position += body.velocity; if body.can_rotate { - apply_rotation(transform, &mut body.velocity, false); + apply_rotation(transform, &mut body.velocity, 0.0, false); } } } @@ -273,8 +286,15 @@ pub fn debug_draw_rigid_bodies(world: &mut World) { } } -fn apply_rotation(transform: &mut Transform, velocity: &mut Vec2, is_on_ground: bool) { - if !is_on_ground { +fn apply_rotation( + transform: &mut Transform, + velocity: &mut Vec2, + angular_velocity: f32, + is_on_ground: bool, +) { + if !angular_velocity.is_zero() { + transform.rotation += deg_to_rad(angular_velocity * get_frame_time()); + } else if !is_on_ground { transform.rotation += velocity.x.abs() * 0.00045 + velocity.y.abs() * 0.00015; } else { transform.rotation %= std::f32::consts::PI * 2.0; diff --git a/src/player/controller.rs b/src/player/controller.rs index 13db86d20e..860305df9c 100644 --- a/src/player/controller.rs +++ b/src/player/controller.rs @@ -18,6 +18,7 @@ impl PlayerControllerKind { } } +#[derive(Clone)] pub struct PlayerController { pub kind: PlayerControllerKind, diff --git a/src/player/inventory.rs b/src/player/inventory.rs index ae801413a7..d95a827be7 100644 --- a/src/player/inventory.rs +++ b/src/player/inventory.rs @@ -25,6 +25,9 @@ pub struct PlayerInventory { pub weapon: Option, pub items: Vec, pub hat: Option, + /// Systems can set this to Some(entity) in order to schedule a replacement of the player's + /// current weapon ( if any ) with the specified weapon. + pub pending_weapon_replacement: Option, } impl PlayerInventory { @@ -39,6 +42,7 @@ impl PlayerInventory { weapon: None, items: Vec::new(), hat: None, + pending_weapon_replacement: None, } } @@ -133,7 +137,15 @@ pub fn update_player_inventory(world: &mut World) { i += 1; } - if controller.should_pickup { + let mut weapon_entity_to_pick_up = None; + + if let Some(we) = inventory.pending_weapon_replacement.take() { + if let Some(weapon_entity) = inventory.weapon.take() { + to_drop.push(weapon_entity); + } + + weapon_entity_to_pick_up = Some(we); + } else if controller.should_pickup { if let Some(weapon_entity) = inventory.weapon.take() { to_drop.push(weapon_entity); @@ -147,26 +159,29 @@ pub fn update_player_inventory(world: &mut World) { body.velocity = velocity; } else if player.pickup_grace_timer >= PICKUP_GRACE_TIME { - for (i, &(weapon_entity, rect)) in weapon_colliders.iter().enumerate() { + for (i, &(we, rect)) in weapon_colliders.iter().enumerate() { if player_rect.overlaps(&rect) { - picked_up.push((entity, weapon_entity)); weapon_colliders.remove(i); + weapon_entity_to_pick_up = Some(we); + break; + } + } + } + } - inventory.weapon = Some(weapon_entity); - player.pickup_grace_timer = 0.0; + if let Some(weapon_entity) = weapon_entity_to_pick_up { + picked_up.push((entity, weapon_entity)); - let mut body = world.get_mut::(weapon_entity).unwrap(); - body.is_deactivated = true; + inventory.weapon = Some(weapon_entity); + player.pickup_grace_timer = 0.0; - let mut drawable = world.get_mut::(weapon_entity).unwrap(); - let sprite_set = drawable.get_animated_sprite_set_mut().unwrap(); + let mut body = world.get_mut::(weapon_entity).unwrap(); + body.is_deactivated = true; - sprite_set.set_all(IDLE_ANIMATION_ID, true); + let mut drawable = world.get_mut::(weapon_entity).unwrap(); + let sprite_set = drawable.get_animated_sprite_set_mut().unwrap(); - break; - } - } - } + sprite_set.set_all(IDLE_ANIMATION_ID, true); } if let Some(weapon_entity) = inventory.weapon {