this repo has no description
1#![allow(clippy::type_complexity)]
2
3use std::collections::VecDeque;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use rand::seq::SliceRandom;
7use rand::Rng;
8use valence::prelude::*;
9use valence::protocol::sound::{Sound, SoundCategory};
10use valence::spawn::IsFlat;
11
12const START_POS: BlockPos = BlockPos::new(0, 100, 0);
13const VIEW_DIST: u8 = 10;
14
15const BLOCK_TYPES: [BlockState; 7] = [
16 BlockState::GRASS_BLOCK,
17 BlockState::OAK_LOG,
18 BlockState::BIRCH_LOG,
19 BlockState::OAK_LEAVES,
20 BlockState::BIRCH_LEAVES,
21 BlockState::DIRT,
22 BlockState::MOSS_BLOCK,
23];
24
25pub fn main() {
26 App::new()
27 .add_plugins(DefaultPlugins)
28 .add_systems(
29 Update,
30 (
31 init_clients,
32 reset_clients.after(init_clients),
33 manage_chunks.after(reset_clients).before(manage_blocks),
34 manage_blocks,
35 despawn_disconnected_clients,
36 ),
37 )
38 .run();
39}
40
41#[derive(Component)]
42struct GameState {
43 blocks: VecDeque<BlockPos>,
44 score: u32,
45 combo: u32,
46 target_y: i32,
47 last_block_timestamp: u128,
48}
49
50fn init_clients(
51 mut clients: Query<
52 (
53 Entity,
54 &mut Client,
55 &mut VisibleChunkLayer,
56 &mut IsFlat,
57 &mut GameMode,
58 ),
59 Added<Client>,
60 >,
61 server: Res<Server>,
62 dimensions: Res<DimensionTypeRegistry>,
63 biomes: Res<BiomeRegistry>,
64 mut commands: Commands,
65) {
66 for (entity, mut client, mut visible_chunk_layer, mut is_flat, mut game_mode) in &mut clients {
67 visible_chunk_layer.0 = entity;
68 is_flat.0 = true;
69 *game_mode = GameMode::Adventure;
70
71 client.send_chat_message("Welcome to epic infinite parkour game!".italic());
72
73 let state = GameState {
74 blocks: VecDeque::new(),
75 score: 0,
76 combo: 0,
77 target_y: 0,
78 last_block_timestamp: 0,
79 };
80
81 let layer = ChunkLayer::new(ident!("overworld"), &dimensions, &biomes, &server);
82
83 commands.entity(entity).insert((state, layer));
84 }
85}
86
87fn reset_clients(
88 mut clients: Query<(
89 &mut Client,
90 &mut Position,
91 &mut Look,
92 &mut GameState,
93 &mut ChunkLayer,
94 )>,
95) {
96 for (mut client, mut pos, mut look, mut state, mut layer) in &mut clients {
97 let out_of_bounds = (pos.0.y as i32) < START_POS.y - 32;
98
99 if out_of_bounds || state.is_added() {
100 if out_of_bounds && !state.is_added() {
101 client.send_chat_message(
102 "Your score was ".italic()
103 + state
104 .score
105 .to_string()
106 .color(Color::GOLD)
107 .bold()
108 .not_italic(),
109 );
110 }
111
112 // Init chunks.
113 for pos in ChunkView::new(START_POS.into(), VIEW_DIST).iter() {
114 layer.insert_chunk(pos, UnloadedChunk::new());
115 }
116
117 state.score = 0;
118 state.combo = 0;
119
120 for block in &state.blocks {
121 layer.set_block(*block, BlockState::AIR);
122 }
123 state.blocks.clear();
124 state.blocks.push_back(START_POS);
125 layer.set_block(START_POS, BlockState::STONE);
126
127 for _ in 0..10 {
128 generate_next_block(&mut state, &mut layer, false);
129 }
130
131 pos.set([
132 f64::from(START_POS.x) + 0.5,
133 f64::from(START_POS.y) + 1.0,
134 f64::from(START_POS.z) + 0.5,
135 ]);
136 look.yaw = 0.0;
137 look.pitch = 0.0;
138 }
139 }
140}
141
142fn manage_blocks(mut clients: Query<(&mut Client, &Position, &mut GameState, &mut ChunkLayer)>) {
143 for (mut client, pos, mut state, mut layer) in &mut clients {
144 let pos_under_player = BlockPos::new(
145 (pos.0.x - 0.5).round() as i32,
146 pos.0.y as i32 - 1,
147 (pos.0.z - 0.5).round() as i32,
148 );
149
150 if let Some(index) = state
151 .blocks
152 .iter()
153 .position(|block| *block == pos_under_player)
154 {
155 if index > 0 {
156 let power_result = 2_f32.powf((state.combo as f32) / 45.0);
157 let max_time_taken = (1000_f32 * (index as f32) / power_result) as u128;
158
159 let current_time_millis = SystemTime::now()
160 .duration_since(UNIX_EPOCH)
161 .unwrap()
162 .as_millis();
163
164 if current_time_millis - state.last_block_timestamp < max_time_taken {
165 state.combo += index as u32
166 } else {
167 state.combo = 0
168 }
169
170 for _ in 0..index {
171 generate_next_block(&mut state, &mut layer, true)
172 }
173
174 let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05;
175 client.play_sound(
176 Sound::BlockNoteBlockBass,
177 SoundCategory::Master,
178 pos.0,
179 1.0,
180 pitch,
181 );
182
183 client.set_title("");
184 client.set_subtitle(state.score.to_string().color(Color::LIGHT_PURPLE).bold());
185 }
186 }
187 }
188}
189
190fn manage_chunks(mut clients: Query<(&Position, &OldPosition, &mut ChunkLayer), With<Client>>) {
191 for (pos, old_pos, mut layer) in &mut clients {
192 let old_view = ChunkView::new(old_pos.get().into(), VIEW_DIST);
193 let view = ChunkView::new(pos.0.into(), VIEW_DIST);
194
195 if old_view != view {
196 for pos in old_view.diff(view) {
197 layer.remove_chunk(pos);
198 }
199
200 for pos in view.diff(old_view) {
201 layer.chunk_entry(pos).or_default();
202 }
203 }
204 }
205}
206
207fn generate_next_block(state: &mut GameState, layer: &mut ChunkLayer, in_game: bool) {
208 if in_game {
209 let removed_block = state.blocks.pop_front().unwrap();
210 layer.set_block(removed_block, BlockState::AIR);
211
212 state.score += 1
213 }
214
215 let last_pos = *state.blocks.back().unwrap();
216 let block_pos = generate_random_block(last_pos, state.target_y);
217
218 if last_pos.y == START_POS.y {
219 state.target_y = 0
220 } else if last_pos.y < START_POS.y - 30 || last_pos.y > START_POS.y + 30 {
221 state.target_y = START_POS.y;
222 }
223
224 let mut rng = rand::thread_rng();
225
226 layer.set_block(block_pos, *BLOCK_TYPES.choose(&mut rng).unwrap());
227 state.blocks.push_back(block_pos);
228
229 // Combo System
230 state.last_block_timestamp = SystemTime::now()
231 .duration_since(UNIX_EPOCH)
232 .unwrap()
233 .as_millis();
234}
235
236fn generate_random_block(pos: BlockPos, target_y: i32) -> BlockPos {
237 let mut rng = rand::thread_rng();
238
239 // if above or below target_y, change y to gradually reach it
240 let y = match target_y {
241 0 => rng.gen_range(-1..2),
242 y if y > pos.y => 1,
243 _ => -1,
244 };
245 let z = match y {
246 1 => rng.gen_range(1..3),
247 -1 => rng.gen_range(2..5),
248 _ => rng.gen_range(1..4),
249 };
250 let x = rng.gen_range(-3..4);
251
252 BlockPos::new(pos.x + x, pos.y + y, pos.z + z)
253}