use crate::ecs::{EditorWorld, PlacementShape};
use nightshade::ecs::camera::systems::first_person_camera_look_system;
use nightshade::ecs::physics::commands::spawn_first_person_player;
use nightshade::ecs::physics::components::{ColliderComponent, RigidBodyComponent};
use nightshade::prelude::*;
const PLAY_GROUND_HALF_EXTENT: f32 = 200.0;
const PLAY_GROUND_THICKNESS: f32 = 1.0;
pub const PLAYER_SPAWN_TAG: &str = "player_spawn";
pub fn add_player_spawn(editor_world: &mut EditorWorld, world: &mut World) {
let focus = crate::systems::camera::focus_point(editor_world, world);
let position = Vec3::new(focus.x, 0.9, focus.z);
let entity = super::retained_ui::build::spawn_shape(world, PlacementShape::Cube, position);
if let Some(local) = world.core.get_local_transform_mut(entity) {
local.translation = position;
local.scale = Vec3::new(0.7, 1.8, 0.7);
}
mark_local_transform_dirty(world, entity);
super::retained_ui::build::apply_to_entity(
world,
11 % super::retained_ui::build::palette_count(),
entity,
);
if let Some(name) = world.core.get_name_mut(entity) {
name.0 = "Player Spawn".to_string();
}
world
.resources
.entities
.tags
.entry(entity)
.or_default()
.push(PLAYER_SPAWN_TAG.to_string());
crate::scene_writeback::register_subtree(
&mut editor_world.resources.project,
&mut editor_world.resources.editor_scene,
world,
entity,
);
let captured =
crate::undo::capture_subtree(world, &mut editor_world.resources.editor_scene, entity);
editor_world.resources.undo.push(
crate::undo::UndoableOperation::EntityCreated {
captured: Box::new(captured),
},
"Add player spawn",
);
crate::systems::selection::set_primary(editor_world, Some(entity));
}
pub fn toggle(editor_world: &mut EditorWorld, world: &mut World) {
if editor_world.resources.play.active {
exit(editor_world, world);
} else {
enter(editor_world, world);
}
}
pub fn toggle_and_sync_button(editor_world: &mut EditorWorld, world: &mut World) {
toggle(editor_world, world);
let label = if editor_world.resources.play.active {
"Stop (Esc)"
} else {
"Play (first person)"
};
let button = editor_world.resources.ui_handles.create_shape.play_button;
ui_button_set_text(world, button, label);
}
pub fn poll_hotkey(editor_world: &mut EditorWorld, world: &mut World) {
if !world.resources.input.keyboard.just_pressed(KeyCode::F5) {
return;
}
if !editor_world.resources.play.active && text_input_focused(world) {
return;
}
toggle_and_sync_button(editor_world, world);
}
fn text_input_focused(world: &World) -> bool {
world
.resources
.retained_ui
.interaction_for_active()
.focused_entity
.is_some_and(|entity| {
world.ui.get_ui_text_input(entity).is_some()
|| world.ui.get_ui_text_area(entity).is_some()
|| world.ui.get_ui_drag_value(entity).is_some()
})
}
fn spawn_point(editor_world: &EditorWorld, world: &World) -> Vec3 {
let tagged = world
.resources
.entities
.tags
.iter()
.find(|(_, tags)| tags.iter().any(|tag| tag == PLAYER_SPAWN_TAG))
.map(|(entity, _)| *entity)
.and_then(|entity| world.core.get_global_transform(entity))
.map(|transform| transform.translation());
match tagged {
Some(position) => Vec3::new(position.x, position.y + 0.6, position.z),
None => {
let focus = crate::systems::camera::focus_point(editor_world, world);
Vec3::new(focus.x, 2.0, focus.z)
}
}
}
fn enter(editor_world: &mut EditorWorld, world: &mut World) {
let spawn = spawn_point(editor_world, world);
let (player, camera) = spawn_first_person_player(world, spawn);
if let Some(controller) = world.core.get_character_controller_mut(player) {
controller.engine_input_enabled = true;
controller.config.max_slope_climb_angle = 55_f32.to_radians();
controller.config.min_slope_slide_angle = 50_f32.to_radians();
}
editor_world
.resources
.editor_scene
.register_scaffolding(player);
editor_world
.resources
.editor_scene
.register_scaffolding(camera);
editor_world.resources.play.restore_camera = world.resources.active_camera;
editor_world.resources.play.player = Some(player);
editor_world.resources.play.camera = Some(camera);
editor_world.resources.play.active = true;
editor_world.resources.play.escape_was_pressed = true;
editor_world.resources.play.ground = Some(spawn_play_ground(world));
world.resources.active_camera = Some(camera);
world.resources.physics.enabled = true;
world.resources.retained_ui.enabled = false;
world.resources.render_settings.render_world_to_swapchain = true;
clear_editor_viewport(world);
set_spawn_markers_visible(world, false);
set_cursor_locked(world, true);
set_cursor_visible(world, false);
}
fn clear_editor_viewport(world: &mut World) {
world.resources.window.active_viewport_rect = None;
world.resources.window.camera_tile_rects.clear();
world.resources.user_interface.required_cameras.clear();
}
fn exit(editor_world: &mut EditorWorld, world: &mut World) {
if let Some(player) = editor_world.resources.play.player.take() {
despawn_recursive_immediate(world, player);
}
if let Some(ground) = editor_world.resources.play.ground.take() {
despawn_recursive_immediate(world, ground);
}
editor_world.resources.play.camera = None;
editor_world.resources.play.active = false;
world.resources.active_camera = editor_world.resources.play.restore_camera.take();
world.resources.physics.enabled = false;
world.resources.retained_ui.enabled = true;
world.resources.render_settings.render_world_to_swapchain = false;
if let Some(camera) = world.resources.active_camera {
world.resources.window.force_render_cameras.insert(camera);
}
set_spawn_markers_visible(world, true);
set_cursor_locked(world, false);
set_cursor_visible(world, true);
}
fn spawn_play_ground(world: &mut World) -> Entity {
use nightshade::ecs::world::{
COLLIDER, GLOBAL_TRANSFORM, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, RIGID_BODY,
};
let entity = spawn_entities(
world,
RIGID_BODY | COLLIDER | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
1,
)[0];
if let Some(local) = world.core.get_local_transform_mut(entity) {
local.translation = Vec3::new(0.0, -PLAY_GROUND_THICKNESS, 0.0);
}
mark_local_transform_dirty(world, entity);
if let Some(rigid_body) = world.core.get_rigid_body_mut(entity) {
*rigid_body =
RigidBodyComponent::new_static().with_translation(0.0, -PLAY_GROUND_THICKNESS, 0.0);
}
if let Some(collider) = world.core.get_collider_mut(entity) {
*collider = ColliderComponent::new_cuboid(
PLAY_GROUND_HALF_EXTENT,
PLAY_GROUND_THICKNESS,
PLAY_GROUND_HALF_EXTENT,
)
.with_friction(0.9);
}
entity
}
fn set_spawn_markers_visible(world: &mut World, visible: bool) {
let markers: Vec<Entity> = world
.resources
.entities
.tags
.iter()
.filter(|(_, tags)| tags.iter().any(|tag| tag == PLAYER_SPAWN_TAG))
.map(|(entity, _)| *entity)
.collect();
for marker in markers {
world.core.add_components(marker, VISIBILITY);
world.core.set_visibility(marker, Visibility { visible });
}
}
pub fn tick(editor_world: &mut EditorWorld, world: &mut World, shell_capturing: bool) {
if !editor_world.resources.play.active {
return;
}
let escape = world
.resources
.input
.keyboard
.is_key_pressed(KeyCode::Escape);
let escape_edge = escape && !editor_world.resources.play.escape_was_pressed && !shell_capturing;
editor_world.resources.play.escape_was_pressed = escape;
if escape_edge {
exit(editor_world, world);
return;
}
clear_editor_viewport(world);
if shell_capturing {
set_cursor_locked(world, false);
set_cursor_visible(world, true);
} else {
set_cursor_locked(world, true);
set_cursor_visible(world, false);
first_person_camera_look_system(world);
}
}