fmc_173/players/
mod.rs

1use std::collections::HashMap;
2
3use fmc::{
4    bevy::math::{DQuat, DVec3},
5    blocks::{BlockPosition, Blocks},
6    database::Database,
7    interfaces::{
8        HeldInterfaceStack, InterfaceEventRegistration, InterfaceEvents, RegisterInterfaceNode,
9    },
10    items::ItemStack,
11    models::{AnimationPlayer, Model, Models},
12    networking::{NetworkEvent, NetworkMessage, Server},
13    physics::{Collider, Physics},
14    players::{Camera, Player},
15    prelude::*,
16    protocol::messages,
17    world::{
18        chunk::{Chunk, ChunkPosition},
19        WorldMap,
20    },
21};
22use serde::{Deserialize, Serialize};
23
24use crate::{
25    items::{crafting::CraftingGrid, DroppedItem},
26    world::WorldProperties,
27};
28
29use self::health::HealthBundle;
30
31mod hand;
32mod health;
33mod inventory_interface;
34mod movement;
35
36pub use hand::{HandHits, HandInteractions};
37pub use health::{HealEvent, Health, PlayerDamageEvent};
38
39pub struct PlayerPlugin;
40impl Plugin for PlayerPlugin {
41    fn build(&self, app: &mut App) {
42        app.add_event::<RespawnEvent>()
43            .add_plugins(inventory_interface::InventoryInterfacePlugin)
44            .add_plugins(health::HealthPlugin)
45            .add_plugins(hand::HandPlugin)
46            .add_plugins(movement::MovementPlugin)
47            .add_systems(
48                Update,
49                (
50                    on_gamemode_update,
51                    (add_players, apply_deferred).chain(),
52                    respawn_players,
53                    rotate_player_model,
54                    discard_items.after(InterfaceEventRegistration),
55                ),
56            )
57            // Save player after all remaining events have been handled. Avoid dupes and other
58            // unexpected behaviour.
59            .add_systems(PostUpdate, save_player_data);
60    }
61}
62
63#[derive(Component, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
64pub enum GameMode {
65    Survival,
66    Creative,
67}
68
69#[derive(Component, Serialize, Deserialize, Deref, DerefMut, Clone)]
70pub struct Inventory {
71    #[deref]
72    inventory: Vec<ItemStack>,
73    equipped_item: usize,
74}
75
76impl Default for Inventory {
77    fn default() -> Self {
78        let capacity = 36;
79        let mut inventory = Vec::with_capacity(capacity);
80        inventory.resize_with(capacity, ItemStack::default);
81
82        Self {
83            inventory,
84            equipped_item: 0,
85        }
86    }
87}
88
89impl Inventory {
90    pub fn held_item_stack(&self) -> &ItemStack {
91        &self[self.equipped_item]
92    }
93
94    pub fn held_item_stack_mut(&mut self) -> &mut ItemStack {
95        let index = self.equipped_item;
96        &mut self[index]
97    }
98}
99
100// TODO: Move this into Inventory, no clue why I separated them
101//
102/// The equipment the
103#[derive(Component, Default, Serialize, Deserialize, Clone)]
104pub struct Equipment {
105    pub helmet: ItemStack,
106    pub chestplate: ItemStack,
107    pub leggings: ItemStack,
108    pub boots: ItemStack,
109}
110
111/// Components a player consists of
112#[derive(Bundle)]
113pub struct PlayerBundle {
114    pub transform: Transform,
115    pub camera: Camera,
116    pub aabb: Collider,
117    pub inventory: Inventory,
118    pub equipment: Equipment,
119    pub crafting_table: CraftingGrid,
120    pub health_bundle: HealthBundle,
121    pub gamemode: GameMode,
122}
123
124impl Default for PlayerBundle {
125    fn default() -> Self {
126        Self {
127            transform: Transform::default(),
128            camera: Camera::default(),
129            aabb: Collider::from_min_max(DVec3::new(-0.3, 0.0, -0.3), DVec3::new(0.3, 1.8, 0.3)),
130            inventory: Inventory::default(),
131            equipment: Equipment::default(),
132            crafting_table: CraftingGrid::with_size(4),
133            health_bundle: HealthBundle::default(),
134            gamemode: GameMode::Survival,
135        }
136    }
137}
138
139impl From<PlayerSave> for PlayerBundle {
140    fn from(save: PlayerSave) -> Self {
141        PlayerBundle {
142            transform: Transform::from_translation(save.position),
143            camera: Camera::new(Transform {
144                translation: save.camera_position,
145                rotation: save.camera_rotation,
146                ..default()
147            }),
148            inventory: save.inventory,
149            equipment: save.equipment,
150            health_bundle: HealthBundle::from_health(save.health),
151            gamemode: save.game_mode,
152            ..default()
153        }
154    }
155}
156
157// TODO: Remember equipped and send to player
158//
159/// The format the player is saved as in the database.
160#[derive(Serialize, Deserialize)]
161pub struct PlayerSave {
162    position: DVec3,
163    camera_position: DVec3,
164    camera_rotation: DQuat,
165    inventory: Inventory,
166    equipment: Equipment,
167    health: Health,
168    game_mode: GameMode,
169}
170
171impl PlayerSave {
172    fn save(&self, username: &str, database: &Database) {
173        let conn = database.get_write_connection();
174
175        let mut stmt = conn
176            .prepare("INSERT OR REPLACE INTO players VALUES (?,?)")
177            .unwrap();
178        let json = serde_json::to_string(self).unwrap();
179
180        stmt.execute(rusqlite::params![username, json]).unwrap();
181    }
182
183    fn load(username: &str, database: &Database) -> Option<Self> {
184        let conn = database.get_read_connection();
185
186        let mut stmt = conn
187            .prepare("SELECT save FROM players WHERE name = ?")
188            .unwrap();
189        let mut rows = if let Ok(rows) = stmt.query([username]) {
190            rows
191        } else {
192            return None;
193        };
194
195        // TODO: I've forgot how you're supposed to do this correctly
196        if let Some(row) = rows.next().unwrap() {
197            let json: String = row.get_unwrap(0);
198            let save: PlayerSave = serde_json::from_str(&json).unwrap();
199            return Some(save);
200        } else {
201            return None;
202        };
203    }
204}
205
206fn add_players(
207    mut commands: Commands,
208    net: Res<Server>,
209    database: Res<Database>,
210    models: Res<Models>,
211    mut respawn_events: EventWriter<RespawnEvent>,
212    mut registration_events: EventWriter<RegisterInterfaceNode>,
213    added_players: Query<(Entity, &Player), Added<Player>>,
214) {
215    for (player_entity, player) in added_players.iter() {
216        let bundle = if let Some(save) = PlayerSave::load(&player.username, &database) {
217            PlayerBundle::from(save)
218        } else {
219            respawn_events.send(RespawnEvent { player_entity });
220            PlayerBundle::default()
221        };
222
223        net.send_one(
224            player_entity,
225            messages::PlayerPosition {
226                position: bundle.transform.translation,
227            },
228        );
229
230        net.send_one(
231            player_entity,
232            messages::PlayerCameraPosition {
233                position: bundle.camera.translation.as_vec3(),
234            },
235        );
236
237        net.send_one(
238            player_entity,
239            messages::PlayerCameraRotation {
240                rotation: bundle.camera.rotation.as_quat(),
241            },
242        );
243
244        let model = models.get_by_name("player");
245
246        let mut animation_player = AnimationPlayer::default();
247        animation_player.set_move_animation(Some(model.animations["walk"]));
248        animation_player.set_idle_animation(Some(model.animations["idle"]));
249        animation_player.set_transition_time(0.15);
250
251        let model_entity = commands
252            .spawn(Model::Asset(model.id))
253            .set_parent(player_entity)
254            .id();
255        animation_player.set_target(model_entity);
256
257        let discard_items_entity = commands.spawn(DiscardItems).id();
258        registration_events.send(RegisterInterfaceNode {
259            player_entity,
260            node_path: "".to_owned(),
261            node_entity: discard_items_entity,
262        });
263
264        commands
265            .entity(player_entity)
266            .insert((bundle, animation_player))
267            .add_child(discard_items_entity);
268    }
269}
270
271fn save_player_data(
272    database: Res<Database>,
273    mut network_events: EventReader<NetworkEvent>,
274    mut players: Query<(
275        &Player,
276        &Transform,
277        &Camera,
278        &Inventory,
279        &Equipment,
280        &Health,
281        &GameMode,
282    )>,
283) {
284    for network_event in network_events.read() {
285        let NetworkEvent::Disconnected { entity } = network_event else {
286            continue;
287        };
288
289        let Ok((player, transform, camera, inventory, equipment, health, game_mode)) =
290            players.get_mut(*entity)
291        else {
292            continue;
293        };
294
295        PlayerSave {
296            position: transform.translation,
297            camera_position: camera.translation,
298            camera_rotation: camera.rotation,
299            inventory: inventory.clone(),
300            equipment: equipment.clone(),
301            health: health.clone(),
302            game_mode: *game_mode,
303        }
304        .save(&player.username, &database);
305    }
306}
307
308#[derive(Event)]
309pub struct RespawnEvent {
310    pub player_entity: Entity,
311}
312
313// TODO: If it can't find a valid spawn point it will just oscillate in an infinite loop between the
314// air chunk above and the one it can't find anything in.
315// TODO: This might take a really long time to compute because of the chunk loading, and should
316// probably be done ahead of time through an async task. Idk if the spawn point should change
317// between each spawn. A good idea if it's really hard to validate that the player won't suffocate
318// infinitely.
319fn respawn_players(
320    net: Res<Server>,
321    world_properties: Res<WorldProperties>,
322    world_map: Res<WorldMap>,
323    database: Res<Database>,
324    mut player_query: Query<&mut Transform, With<Player>>,
325    mut heal_events: EventWriter<HealEvent>,
326    mut respawn_events: EventReader<RespawnEvent>,
327) {
328    for respawn_event in respawn_events.read() {
329        let blocks = Blocks::get();
330        let air = blocks.get_id("air");
331
332        let mut chunk_position = ChunkPosition::from(world_properties.spawn_point.center);
333        let spawn_position = 'outer: loop {
334            let chunk = futures_lite::future::block_on(Chunk::load(
335                chunk_position,
336                world_map.terrain_generator.clone(),
337                database.clone(),
338            ))
339            .1;
340
341            if chunk.is_uniform() && chunk[0] == air {
342                break BlockPosition::from(chunk_position);
343            }
344
345            // Find two consecutive air blocks to spawn in
346            for (i, block_column) in chunk.blocks.chunks_exact(Chunk::SIZE).enumerate() {
347                let mut count = 0;
348                for (j, block) in block_column.iter().enumerate() {
349                    if count == 0 && *block == air {
350                        count += 1;
351                    } else if count == 1 && *block == air {
352                        let mut spawn_position = BlockPosition::from(chunk_position)
353                            + BlockPosition::from(i * Chunk::SIZE + j);
354                        spawn_position.y -= 1;
355                        break 'outer spawn_position;
356                    } else {
357                        count = 0;
358                    }
359                }
360            }
361
362            chunk_position.y += Chunk::SIZE as i32;
363        };
364
365        let spawn_position = spawn_position.as_dvec3() + DVec3::new(0.5, 0.0, 0.5);
366
367        // TODO: Because of the latency before the client reports back its new position, the player will
368        // be alive for a small moment at the spot they died, picking up their items again. So we
369        // have to set the position server side too.
370        let mut player_transform = player_query.get_mut(respawn_event.player_entity).unwrap();
371        player_transform.translation = spawn_position;
372
373        heal_events.send(HealEvent {
374            player_entity: respawn_event.player_entity,
375            healing: u32::MAX,
376        });
377
378        net.send_one(
379            respawn_event.player_entity,
380            messages::PlayerPosition {
381                position: spawn_position,
382            },
383        );
384    }
385}
386
387// TODO: This rotates the main player transform and lets propagation take care of the model.
388// Propagation takes a long time to be sent to the clients because of unfortunate system ordering.
389// This needs to be fixed on its own, but it will also become necessary to handle the player's
390// models directly, as there will be a small collection of them.
391fn rotate_player_model(
392    mut player_query: Query<&mut Transform, With<Player>>,
393    mut camera_rotation_events: EventReader<NetworkMessage<messages::PlayerCameraRotation>>,
394) {
395    for rotation_update in camera_rotation_events.read() {
396        let mut transform = player_query.get_mut(rotation_update.player_entity).unwrap();
397
398        let rotation = rotation_update.rotation.as_dquat();
399
400        let theta = rotation.y.atan2(rotation.w);
401        transform.rotation = DQuat::from_xyzw(0.0, theta.sin(), 0.0, theta.cos());
402    }
403}
404
405fn on_gamemode_update(
406    net: Res<Server>,
407    player_query: Query<(Entity, &GameMode), Changed<GameMode>>,
408    mut current_movement_function: Local<HashMap<Entity, Option<String>>>,
409) {
410    for (player_entity, gamemode) in player_query.iter() {
411        let current_movement_function = current_movement_function.entry(player_entity).or_default();
412        if let Some(name) = current_movement_function.take() {
413            net.send_one(player_entity, messages::Plugin::Disable(name));
414        }
415
416        match gamemode {
417            GameMode::Creative => {
418                let mut health_visibility = messages::InterfaceNodeVisibilityUpdate::default();
419                health_visibility.set_hidden("health".to_owned());
420                net.send_one(player_entity, health_visibility);
421
422                net.send_one(
423                    player_entity,
424                    messages::Plugin::Enable("creative".to_owned()),
425                );
426                *current_movement_function = Some("creative".to_owned());
427            }
428            GameMode::Survival => {
429                let mut health_visibility = messages::InterfaceNodeVisibilityUpdate::default();
430                health_visibility.set_visible("health".to_owned());
431                net.send_one(player_entity, health_visibility);
432
433                net.send_one(
434                    player_entity,
435                    messages::Plugin::Enable("movement".to_owned()),
436                );
437                *current_movement_function = Some("movement".to_owned());
438            }
439        }
440    }
441}
442
443#[derive(Component)]
444struct DiscardItems;
445
446fn discard_items(
447    mut commands: Commands,
448    mut inventory_query: Query<(&mut HeldInterfaceStack, &GlobalTransform, &Camera), With<Player>>,
449    mut interface_events: Query<
450        (&mut InterfaceEvents, &Parent),
451        (Changed<InterfaceEvents>, With<DiscardItems>),
452    >,
453) {
454    for (mut interface_events, parent) in interface_events.iter_mut() {
455        let (mut held_item, transform, camera) = inventory_query.get_mut(parent.get()).unwrap();
456        for event in interface_events.read() {
457            if let messages::InterfaceInteraction::PlaceItem { quantity, .. } = *event {
458                let discarded = held_item.take(quantity);
459                if discarded.size() == 0 {
460                    continue;
461                }
462
463                let dropped_item_position =
464                    transform.translation() + camera.translation + camera.forward();
465                commands.spawn((
466                    DroppedItem::new(discarded),
467                    Transform::from_translation(dropped_item_position),
468                    Physics {
469                        velocity: camera.forward() * 12.0,
470                        ..default()
471                    },
472                ));
473            }
474        }
475    }
476}