Skip to main content

chunkedge_server/
passenger.rs

1//! Passenger/riding API built on Bevy's entity relationships.
2//!
3//! Insert [`Riding`] on a passenger entity to mount it onto a vehicle; the
4//! vehicle's [`Passengers`] component and the clients viewing it are kept in
5//! sync automatically.
6
7use std::borrow::Cow;
8
9use bevy_app::prelude::*;
10use bevy_ecs::prelude::*;
11use bevy_ecs::relationship::RelationshipTarget;
12use chunkedge_entity::{EntityId, EntityLayerId, Position};
13use derive_more::Deref;
14
15use crate::client::{
16    Client, FlushPacketsSet, LoadEntityForClientMessage, ViewDistance, VisibleEntityLayers,
17};
18use crate::layer::UpdateLayersPreClientSet;
19use crate::protocol::packets::play::SetPassengersS2c;
20use crate::protocol::{VarInt, WritePacket};
21use crate::{ChunkView, EntityLayer, Layer};
22
23pub struct PassengerPlugin;
24
25impl Plugin for PassengerPlugin {
26    fn build(&self, app: &mut App) {
27        app.add_systems(
28            PostUpdate,
29            (
30                (send_passengers, send_empty_passengers_on_removal)
31                    .before(UpdateLayersPreClientSet),
32                send_passengers_on_entity_load.before(FlushPacketsSet),
33            ),
34        );
35    }
36}
37
38/// Placed on a passenger entity, pointing at the vehicle it rides.
39///
40/// Inserting this component mounts the entity; removing it (or despawning the
41/// entity) dismounts it. The vehicle's [`Passengers`] component is kept in sync
42/// automatically.
43#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)]
44#[relationship(relationship_target = Passengers)]
45pub struct Riding(pub Entity);
46
47/// Automatically maintained on a vehicle entity; lists the entities currently
48/// riding it.
49///
50/// This is populated by inserting
51/// [`Riding`] on passenger entities.
52/// You can access the passenger entities using `.iter()`
53#[derive(Component, Debug)]
54#[relationship_target(relationship = Riding)]
55pub struct Passengers(Vec<Entity>);
56
57fn send_passengers(
58    vehicles: Query<(&EntityId, &Passengers, &EntityLayerId, &Position), Changed<Passengers>>,
59    entity_ids: Query<&EntityId>,
60    mut clients: Query<(
61        &mut Client,
62        &EntityId,
63        &Position,
64        &ViewDistance,
65        &VisibleEntityLayers,
66    )>,
67) {
68    for (vehicle_id, passengers, layer_id, vehicle_pos) in &vehicles {
69        for (mut client, client_id, client_pos, view_distance, visible_layers) in &mut clients {
70            if !visible_layers.0.contains(&layer_id.0) {
71                continue;
72            }
73
74            let view = ChunkView::new(client_pos.0.into(), view_distance.get());
75            if !view.contains(vehicle_pos.0.into()) {
76                continue;
77            }
78
79            client.write_packet(&packet_for_viewer(
80                client_id.get(),
81                vehicle_id.get(),
82                passengers,
83                &entity_ids,
84            ));
85        }
86    }
87}
88
89fn send_empty_passengers_on_removal(
90    mut removed: RemovedComponents<Passengers>,
91    vehicles: Query<(&EntityId, &EntityLayerId, &Position), Without<Passengers>>,
92    mut entity_layers: Query<&mut EntityLayer>,
93) {
94    for entity in removed.read() {
95        let Ok((vehicle_id, layer_id, pos)) = vehicles.get(entity) else {
96            continue;
97        };
98        let Ok(mut entity_layer) = entity_layers.get_mut(layer_id.0) else {
99            continue;
100        };
101
102        let packet = SetPassengersS2c {
103            entity_id: VarInt(vehicle_id.get()),
104            passengers: Cow::Borrowed(&[]),
105        };
106
107        entity_layer.view_writer(pos.0).write_packet(&packet);
108    }
109}
110
111fn send_passengers_on_entity_load(
112    mut messages: MessageReader<LoadEntityForClientMessage>,
113    mut clients: Query<(&mut Client, &EntityId)>,
114    vehicles: Query<(&EntityId, &Passengers)>,
115    entity_ids: Query<&EntityId>,
116) {
117    for message in messages.read() {
118        let Ok((vehicle_id, passengers)) = vehicles.get(message.entity_loaded) else {
119            continue;
120        };
121        let Ok((mut client, client_id)) = clients.get_mut(message.client) else {
122            continue;
123        };
124
125        client.write_packet(&packet_for_viewer(
126            client_id.get(),
127            vehicle_id.get(),
128            passengers,
129            &entity_ids,
130        ));
131    }
132}
133
134fn packet_for_viewer<'a>(
135    viewer_id: i32,
136    vehicle_id: i32,
137    passengers: &Passengers,
138    entity_ids: &Query<&EntityId>,
139) -> SetPassengersS2c<'a> {
140    let ids: Vec<VarInt> = passengers
141        .iter()
142        .filter_map(|passenger| entity_ids.get(passenger).ok())
143        .map(|id| VarInt(rewrite_self(id.get(), viewer_id)))
144        .collect();
145
146    SetPassengersS2c {
147        entity_id: VarInt(rewrite_self(vehicle_id, viewer_id)),
148        passengers: Cow::Owned(ids),
149    }
150}
151
152fn rewrite_self(id: i32, viewer_id: i32) -> i32 {
153    if id == viewer_id { 0 } else { id }
154}