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