diff --git a/assets/map/levels/level1.map.yaml b/assets/map/levels/level1.map.yaml index 2f4850b647..17db83782d 100644 --- a/assets/map/levels/level1.map.yaml +++ b/assets/map/levels/level1.map.yaml @@ -52,7 +52,7 @@ background_layers: position: - 0.0 - 360.0 -background_color: 7EA8A6FF +background_color: 5B5772FF grid_size: - 27 - 21 diff --git a/assets/map/levels/level2.map.yaml b/assets/map/levels/level2.map.yaml index 7cc7f7ffd1..08fd8c1086 100644 --- a/assets/map/levels/level2.map.yaml +++ b/assets/map/levels/level2.map.yaml @@ -52,7 +52,7 @@ background_layers: position: - 0.0 - 360.0 -background_color: 7EA8A6FF +background_color: 5B5772FF grid_size: - 34 - 14 diff --git a/assets/map/levels/level3.map.yaml b/assets/map/levels/level3.map.yaml index 9c9bfcf682..58ed8e03ba 100644 --- a/assets/map/levels/level3.map.yaml +++ b/assets/map/levels/level3.map.yaml @@ -52,7 +52,7 @@ background_layers: position: - 0.0 - 360.0 -background_color: 7EA8A6FF +background_color: 5B5772FF grid_size: - 27 - 22 diff --git a/assets/map/levels/level4.map.yaml b/assets/map/levels/level4.map.yaml index 87e951e2ff..4f2d4fd590 100644 --- a/assets/map/levels/level4.map.yaml +++ b/assets/map/levels/level4.map.yaml @@ -52,7 +52,7 @@ background_layers: position: - 0.0 - 360.0 -background_color: 7EA8A6FF +background_color: 5B5772FF grid_size: - 35 - 15 diff --git a/src/camera.rs b/src/camera.rs index fde0c76d2a..10335a5745 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,7 +1,11 @@ use bevy::render::view::RenderLayers; use bevy_parallax::ParallaxCameraComponent; -use crate::{metadata::GameMeta, player::PlayerIdx, prelude::*}; +use crate::{ + metadata::{GameMeta, MapMeta}, + player::PlayerIdx, + prelude::*, +}; pub struct CameraPlugin; @@ -89,6 +93,7 @@ pub fn spawn_editor_camera(commands: &mut Commands) -> Entity { fn camera_controller( players: Query<&Transform, With>, + map: Query<&MapMeta>, mut camera: Query< (&mut Transform, &mut OrthographicProjection), (With, Without), @@ -102,6 +107,10 @@ fn camera_controller( const ZOOM_OUT_LERP_FACTOR: f32 = 0.1; const MIN_BOUND: f32 = 350.0; + let Ok(map) = map.get_single() else { + return; + }; + let Ok((mut camera_transform, mut projection)) = camera.get_single_mut() else { return; }; @@ -111,6 +120,11 @@ fn camera_controller( let default_height = game.camera_height as f32; let default_width = window_aspect * default_height; + let map_width = (map.tile_size.x * map.grid_size.x) as f32; + let map_height = (map.tile_size.y * map.grid_size.y) as f32; + let min_player_pos = Vec2::new(-CAMERA_PADDING, -CAMERA_PADDING); + let max_player_pos = Vec2::new(map_width + CAMERA_PADDING, map_height + CAMERA_PADDING); + let mut middle_point = Vec2::ZERO; let mut min = Vec2::new(100000.0, 100000.0); let mut max = Vec2::new(-100000.0, -100000.0); @@ -119,6 +133,7 @@ fn camera_controller( for player_transform in &players { let pos = player_transform.translation.truncate(); + let pos = pos.max(min_player_pos).min(max_player_pos); middle_point += pos; min.x = pos.x.min(min.x); diff --git a/src/map.rs b/src/map.rs index d4d22e21f8..eec84e11be 100644 --- a/src/map.rs +++ b/src/map.rs @@ -9,6 +9,7 @@ use crate::{ metadata::{MapElementMeta, MapLayerKind, MapLayerMeta, MapMeta}, name::EntityName, physics::collisions::{CollisionLayerTag, TileCollision}, + player::{PlayerIdx, PlayerKillCommand}, prelude::*, session::SessionManager, utils::Sort, @@ -29,6 +30,12 @@ impl Plugin for MapPlugin { .register_rollback_type::() .register_rollback_type::>() }) + .extend_rollback_schedule(|schedule| { + schedule.add_system_to_stage( + RollbackStage::Last, + handle_out_of_bounds_players_and_items, + ); + }) .add_plugin(elements::MapElementsPlugin); } } @@ -39,6 +46,12 @@ impl Plugin for MapPlugin { #[reflect(Component, Default)] pub struct MapElementHydrated; +/// If this component and a [`Transform`] component is added to any entity, it will be moved back to +/// given position if the entity ever ends up outside the map bounds. +#[derive(Deref, DerefMut, Component, Reflect, Default, Debug)] +#[reflect(Default, Component)] +pub struct MapRespawnPoint(pub Vec3); + // /// Contains the scripts that have been added for the currently loaded map // #[derive(Deref, DerefMut, Default)] // pub struct MapScripts(pub HashSet>); @@ -255,3 +268,38 @@ pub fn hydrate_map( // Start the game session session_manager.start_session(); } + +fn handle_out_of_bounds_players_and_items( + mut commands: Commands, + map: Query<&MapMeta>, + players: Query<(Entity, &Transform), With>, + mut items: Query<(&mut Transform, &MapRespawnPoint), Without>, +) { + const KILL_ZONE_BORDER: f32 = 500.0; + let Ok(map) = map.get_single() else { + return; + }; + + let map_width = (map.grid_size.x * map.tile_size.x) as f32; + let left_kill_zone = -KILL_ZONE_BORDER; + let right_kill_zone = map_width + KILL_ZONE_BORDER; + let bottom_kill_zone = -KILL_ZONE_BORDER; + + // Kill out of bounds players + for (player_ent, transform) in &players { + let pos = transform.translation; + + if pos.x < left_kill_zone || pos.x > right_kill_zone || pos.y < bottom_kill_zone { + commands.add(PlayerKillCommand::new(player_ent)); + } + } + + // Reset out of bound item positions + for (mut transform, respawn_point) in &mut items { + let pos = transform.translation; + + if pos.x < left_kill_zone || pos.x > right_kill_zone || pos.y < bottom_kill_zone { + transform.translation = respawn_point.0; + } + } +} diff --git a/src/map/elements.rs b/src/map/elements.rs index 25bc063b37..eac51f17b2 100644 --- a/src/map/elements.rs +++ b/src/map/elements.rs @@ -3,7 +3,7 @@ use crate::{ damage::{DamageRegion, DamageRegionOwner}, item::{Item, ItemDropped, ItemUsed}, lifetime::Lifetime, - map::MapElementHydrated, + map::{MapElementHydrated, MapRespawnPoint}, metadata::{BuiltinElementKind, MapElementMeta}, name::EntityName, physics::{collisions::CollisionWorld, KinematicBody}, diff --git a/src/map/elements/grenade.rs b/src/map/elements/grenade.rs index bc16a9cd8d..437f68efe1 100644 --- a/src/map/elements/grenade.rs +++ b/src/map/elements/grenade.rs @@ -98,6 +98,7 @@ fn pre_update_in_game( }) .insert(map_element_handle.clone_weak()) .insert_bundle(VisibilityBundle::default()) + .insert(MapRespawnPoint(transform.translation)) .insert_bundle(TransformBundle { local: *transform, ..default() diff --git a/src/map/elements/sword.rs b/src/map/elements/sword.rs index 5be3551386..035004609c 100644 --- a/src/map/elements/sword.rs +++ b/src/map/elements/sword.rs @@ -63,6 +63,7 @@ fn pre_update_in_game( ..default() }) .insert_bundle(VisibilityBundle::default()) + .insert(MapRespawnPoint(transform.translation)) .insert_bundle(TransformBundle { local: *transform, ..default() diff --git a/src/ui/main_menu/map_select.rs b/src/ui/main_menu/map_select.rs index 9498804044..8ac60f6dfc 100644 --- a/src/ui/main_menu/map_select.rs +++ b/src/ui/main_menu/map_select.rs @@ -7,6 +7,7 @@ use crate::{ client::NetClient, proto::{match_setup::MatchSetupMessage, ReliableGameMessageKind}, }, + player::input::WantsGamePause, ui::pause_menu::PauseMenuPage, }; @@ -135,6 +136,7 @@ impl<'w, 's> WidgetSystem for MapSelectMenu<'w, 's> { *params.menu_page = MenuPage::Home; params.reset_manager.reset_world(); params.commands.spawn().insert(map_handle.clone_weak()); + params.commands.insert_resource(WantsGamePause(false)); params .commands .insert_resource(NextState(GameState::InGame));