1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 23:44:38 +00:00

Add TicksAlive component

This commit is contained in:
Kumpelinus 2025-07-11 18:39:11 +02:00
commit 9e1952de7a
7 changed files with 102 additions and 16 deletions

View file

@ -705,6 +705,8 @@ async fn run_schedule_loop(ecs: Arc<Mutex<World>>, outer_schedule_label: Interne
"GameTick is more than 10 ticks behind, skipping ticks so we don't have to burst too much" "GameTick is more than 10 ticks behind, skipping ticks so we don't have to burst too much"
); );
*last_tick = now; *last_tick = now;
// TODO: do we increment TickComponent here?
} }
} else { } else {
last_tick = Some(now); last_tick = Some(now);

View file

@ -128,6 +128,9 @@ impl Default for Hunger {
} }
} }
#[derive(Component, Clone, Debug, Default)]
pub struct TicksAlive(pub u64);
impl InstanceHolder { impl InstanceHolder {
/// Create a new `InstanceHolder` for the given entity. /// Create a new `InstanceHolder` for the given entity.
/// ///

View file

@ -11,7 +11,7 @@ use tracing::info;
use super::login::IsAuthenticated; use super::login::IsAuthenticated;
use crate::{ use crate::{
chat_signing, client::JoinedClientBundle, connection::RawConnection, loading::HasClientLoaded, chat_signing, client::JoinedClientBundle, connection::RawConnection, loading::HasClientLoaded,
local_player::InstanceHolder, local_player::{InstanceHolder, TicksAlive},
}; };
pub struct DisconnectPlugin; pub struct DisconnectPlugin;
@ -72,6 +72,8 @@ pub struct RemoveOnDisconnectBundle {
pub is_authenticated: IsAuthenticated, pub is_authenticated: IsAuthenticated,
// send ServerboundPlayerLoaded next time we join. // send ServerboundPlayerLoaded next time we join.
pub has_client_loaded: HasClientLoaded, pub has_client_loaded: HasClientLoaded,
// TickCounter is reset on reconnect
pub ticks_alive: TicksAlive,
} }
/// A system that removes the several components from our clients when they get /// A system that removes the several components from our clients when they get

View file

@ -22,6 +22,7 @@ pub mod pong;
pub mod respawn; pub mod respawn;
pub mod task_pool; pub mod task_pool;
pub mod tick_broadcast; pub mod tick_broadcast;
pub mod tick_counter;
pub mod tick_end; pub mod tick_end;
/// This plugin group will add all the default plugins necessary for Azalea to /// This plugin group will add all the default plugins necessary for Azalea to
@ -54,6 +55,7 @@ impl PluginGroup for DefaultPlugins {
.add(loading::PlayerLoadedPlugin) .add(loading::PlayerLoadedPlugin)
.add(brand::BrandPlugin) .add(brand::BrandPlugin)
.add(tick_broadcast::TickBroadcastPlugin) .add(tick_broadcast::TickBroadcastPlugin)
.add(tick_counter::TickCounterPlugin)
.add(pong::PongPlugin) .add(pong::PongPlugin)
.add(connection::ConnectionPlugin) .add(connection::ConnectionPlugin)
.add(login::LoginPlugin) .add(login::LoginPlugin)

View file

@ -22,22 +22,9 @@ pub use events::*;
use tracing::{debug, error, trace, warn}; use tracing::{debug, error, trace, warn};
use crate::{ use crate::{
ClientInformation, block_update::QueuedServerBlockUpdates, chat::{ChatPacket, ChatReceivedEvent}, chunks, connection::RawConnection, declare_packet_handlers, disconnect::DisconnectEvent, interact::BlockStatePredictionHandler, inventory::{
block_update::QueuedServerBlockUpdates,
chat::{ChatPacket, ChatReceivedEvent},
chunks,
connection::RawConnection,
declare_packet_handlers,
disconnect::DisconnectEvent,
interact::BlockStatePredictionHandler,
inventory::{
ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
}, }, loading::HasClientLoaded, local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList, TicksAlive}, movement::{KnockbackEvent, KnockbackType}, packet::as_system, player::{GameProfileComponent, PlayerInfo}, ClientInformation
loading::HasClientLoaded,
local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList},
movement::{KnockbackEvent, KnockbackType},
packet::as_system,
player::{GameProfileComponent, PlayerInfo},
}; };
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) { pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) {
@ -299,6 +286,7 @@ impl GamePacketHandler<'_> {
previous: p.common.previous_game_type.into(), previous: p.common.previous_game_type.into(),
}, },
entity_bundle, entity_bundle,
TicksAlive(0),
)); ));
azalea_entity::indexing::add_entity_to_indexes( azalea_entity::indexing::add_entity_to_indexes(

View file

@ -0,0 +1,32 @@
use azalea_core::tick::GameTick;
use azalea_physics::PhysicsSet;
use azalea_world::InstanceName;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use crate::{local_player::TicksAlive, mining::MiningSet, movement::send_position, tick_broadcast::send_tick_broadcast};
/// Inserts the counter-increment system into the `GameTick` schedule **before**
/// physics, mining and movement.
pub struct TickCounterPlugin;
impl Plugin for TickCounterPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
GameTick,
increment_counter
.before(PhysicsSet)
.before(MiningSet)
.before(send_position)
.before(send_tick_broadcast),
);
}
}
/// Increment the [`GameTickCounter`] on every entity that lives in an instance.
fn increment_counter(mut query: Query<&mut TicksAlive, With<InstanceName>>) {
for mut counter in &mut query {
counter.0 += 1;
}
}

View file

@ -0,0 +1,57 @@
use azalea_client::{local_player::TicksAlive, test_utils::prelude::*};
use azalea_core::resource_location::ResourceLocation;
use azalea_protocol::packets::{config::{ClientboundFinishConfiguration, ClientboundRegistryData}, ConnectionProtocol};
use azalea_registry::{DataRegistry, DimensionType};
use simdnbt::owned::{NbtCompound, NbtTag};
#[test]
fn counter_increments_and_resets_on_disconnect() {
init_tracing();
let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
simulation.receive_packet(ClientboundRegistryData {
registry_id: ResourceLocation::new("minecraft:dimension_type"),
entries: vec![(
ResourceLocation::new("minecraft:overworld"),
Some(NbtCompound::from_values(vec![
("height".into(), NbtTag::Int(384)),
("min_y".into(), NbtTag::Int(-64)),
])),
)]
.into_iter()
.collect(),
});
simulation.receive_packet(ClientboundFinishConfiguration);
simulation.tick();
// we need a second tick to handle the state switch properly
simulation.tick();
assert!(!simulation.has_component::<TicksAlive>());
simulation.receive_packet(make_basic_login_packet(
DimensionType::new_raw(0), // overworld
ResourceLocation::new("minecraft:overworld"),
));
simulation.tick();
assert!(simulation.has_component::<TicksAlive>());
assert_eq!(simulation.component::<TicksAlive>().0, 1);
// Tick three times; counter should read 2, 3, 4.
for expected in 2..=4 {
simulation.tick();
let counter = simulation.component::<TicksAlive>();
assert_eq!(
counter.0, expected,
"after {expected} tick(s) counter should be {expected}"
);
}
simulation.disconnect();
simulation.tick();
assert!(!simulation.has_component::<TicksAlive>());
}