steel_core/portal/
end_portal.rs1use std::sync::Arc;
4
5use glam::DVec3;
6use steel_protocol::packets::game::RelativeMovement;
7use steel_utils::{BlockPos, ChunkPos, Direction, SectionPos};
8
9use crate::{
10 entity::Entity,
11 level_data::RespawnData,
12 portal::{PortalTicketTarget, TeleportPostTransition, TeleportTransition},
13 world::World,
14};
15
16pub(crate) const END_SPAWN_POINT: BlockPos = BlockPos::new(100, 50, 0);
18
19const END_PLATFORM_PREWARM_CHUNK_RADIUS: u8 = 1;
20
21#[must_use]
23pub(crate) const fn end_platform_prewarm_center() -> ChunkPos {
24 prewarm_center(END_SPAWN_POINT)
25}
26
27#[must_use]
29pub(crate) const fn end_platform_prewarm_chunk_radius() -> u8 {
30 END_PLATFORM_PREWARM_CHUNK_RADIUS
31}
32
33#[must_use]
35pub(crate) const fn prewarm_center(pos: BlockPos) -> ChunkPos {
36 ChunkPos::new(
37 SectionPos::block_to_section_coord(pos.x()),
38 SectionPos::block_to_section_coord(pos.z()),
39 )
40}
41
42#[must_use]
44pub(crate) fn calculate_entry_transition(
45 target_world: &Arc<World>,
46 entity: &dyn Entity,
47) -> Option<TeleportTransition> {
48 if !target_world.create_end_platform(end_platform_origin()) {
49 log::error!("Unable to create End platform at {}", target_world.key);
50 return None;
51 }
52
53 Some(TeleportTransition {
54 target_world: target_world.clone(),
55 position: end_entry_position(entity.as_player().is_some()),
56 rotation: (Direction::West.to_yaw(), 0.0),
57 velocity: DVec3::ZERO,
58 relatives: RelativeMovement::DELTA.union(RelativeMovement::new(RelativeMovement::X_ROT)),
59 portal_cooldown: entity.dimension_changing_delay(),
60 as_passenger: false,
61 post_transition: portal_sound_then_destination_ticket(),
62 })
63}
64
65#[must_use]
67pub(crate) fn calculate_entity_return_transition(
68 target_world: &Arc<World>,
69 entity: &dyn Entity,
70 respawn_data: &RespawnData,
71) -> TeleportTransition {
72 let spawn_pos = target_world.adjust_spawn_location(respawn_data.pos());
73 TeleportTransition {
74 target_world: target_world.clone(),
75 position: block_bottom_center(spawn_pos),
76 rotation: (respawn_data.yaw, respawn_data.pitch),
77 velocity: DVec3::ZERO,
78 relatives: RelativeMovement::DELTA.union(RelativeMovement::ROTATION),
79 portal_cooldown: entity.dimension_changing_delay(),
80 as_passenger: false,
81 post_transition: portal_sound_then_destination_ticket(),
82 }
83}
84
85#[must_use]
92pub(crate) fn calculate_player_return_transition(
93 target_world: &Arc<World>,
94 entity: &dyn Entity,
95 position: DVec3,
96 rotation: (f32, f32),
97) -> TeleportTransition {
98 TeleportTransition {
99 target_world: target_world.clone(),
100 position,
101 rotation,
102 velocity: DVec3::ZERO,
103 relatives: RelativeMovement::NONE,
104 portal_cooldown: entity.dimension_changing_delay(),
105 as_passenger: false,
106 post_transition: TeleportPostTransition::do_nothing(),
107 }
108}
109
110const fn end_platform_origin() -> BlockPos {
111 BlockPos::new(
112 END_SPAWN_POINT.x(),
113 END_SPAWN_POINT.y() - 1,
114 END_SPAWN_POINT.z(),
115 )
116}
117
118fn end_entry_position(is_player: bool) -> DVec3 {
119 let mut position = block_bottom_center(END_SPAWN_POINT);
120 if is_player {
121 position.y -= 1.0;
122 }
123 position
124}
125
126fn portal_sound_then_destination_ticket() -> TeleportPostTransition {
127 TeleportPostTransition::play_portal_sound().then(TeleportPostTransition::place_portal_ticket(
128 PortalTicketTarget::Destination,
129 ))
130}
131
132fn block_bottom_center(pos: BlockPos) -> DVec3 {
133 let (x, y, z) = pos.get_bottom_center();
134 DVec3::new(x, y, z)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{
140 END_SPAWN_POINT, block_bottom_center, end_entry_position, end_platform_origin,
141 end_platform_prewarm_center, end_platform_prewarm_chunk_radius,
142 };
143 use glam::DVec3;
144 use steel_utils::{BlockPos, ChunkPos};
145
146 #[test]
147 fn end_platform_origin_is_below_vanilla_end_spawn() {
148 assert_eq!(END_SPAWN_POINT, BlockPos::new(100, 50, 0));
149 assert_eq!(end_platform_origin(), BlockPos::new(100, 49, 0));
150 }
151
152 #[test]
153 fn end_entry_player_position_is_one_block_below_spawn_center() {
154 assert_eq!(
155 end_entry_position(false),
156 block_bottom_center(BlockPos::new(100, 50, 0))
157 );
158 assert_eq!(end_entry_position(true), DVec3::new(100.5, 49.0, 0.5));
159 }
160
161 #[test]
162 fn end_platform_prewarm_covers_negative_z_edge() {
163 assert_eq!(end_platform_prewarm_center(), ChunkPos::new(6, 0));
164 assert_eq!(end_platform_prewarm_chunk_radius(), 1);
165 }
166}