From b783e9f6ec800d5a625704adff38c465337ec6e8 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Tue, 4 Mar 2025 18:22:07 +0100 Subject: [PATCH 01/13] feat: create vanilla-behaviors crate + gamemode command --- Cargo.lock | 18 +++ Cargo.toml | 4 + crates/hyperion-respawn/src/lib.rs | 11 +- crates/hyperion/src/egress/player_join/mod.rs | 40 +++-- crates/hyperion/src/simulation/mod.rs | 44 +++++- crates/vanilla-behaviors/.gitignore | 1 + crates/vanilla-behaviors/Cargo.toml | 23 +++ crates/vanilla-behaviors/README.md | 3 + crates/vanilla-behaviors/src/command.rs | 1 + .../vanilla-behaviors/src/command/gamemode.rs | 48 ++++++ crates/vanilla-behaviors/src/lib.rs | 23 +++ crates/vanilla-behaviors/src/module.rs | 1 + .../src/module/natural_damage.rs | 137 ++++++++++++++++++ events/tag/Cargo.toml | 1 + events/tag/src/command.rs | 2 + events/tag/src/lib.rs | 5 +- events/tag/src/module.rs | 1 - events/tag/src/module/damage.rs | 75 ---------- events/tag/src/module/regeneration.rs | 16 +- 19 files changed, 341 insertions(+), 113 deletions(-) create mode 100644 crates/vanilla-behaviors/.gitignore create mode 100644 crates/vanilla-behaviors/Cargo.toml create mode 100644 crates/vanilla-behaviors/README.md create mode 100644 crates/vanilla-behaviors/src/command.rs create mode 100644 crates/vanilla-behaviors/src/command/gamemode.rs create mode 100644 crates/vanilla-behaviors/src/lib.rs create mode 100644 crates/vanilla-behaviors/src/module.rs create mode 100644 crates/vanilla-behaviors/src/module/natural_damage.rs delete mode 100644 events/tag/src/module/damage.rs diff --git a/Cargo.lock b/Cargo.lock index ef5e3ae2..f0c99412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5746,6 +5746,7 @@ dependencies = [ "uuid", "valence_protocol", "valence_server", + "vanilla-behaviors", ] [[package]] @@ -6580,6 +6581,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vanilla-behaviors" +version = "0.1.0" +dependencies = [ + "clap", + "fastrand 2.3.0", + "flecs_ecs", + "geometry", + "hyperion", + "hyperion-clap", + "hyperion-permission", + "hyperion-utils", + "tracing", + "valence_protocol", + "valence_server", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 2e535600..63fe4764 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ 'crates/hyperion-utils', 'crates/simd-utils', 'crates/system-order', + 'crates/vanilla-behaviors', 'events/tag', 'tools/packet-inspector', 'tools/rust-mc-bot', @@ -203,6 +204,9 @@ path = 'crates/hyperion-text' [workspace.dependencies.hyperion-utils] path = 'crates/hyperion-utils' +[workspace.dependencies.vanilla-behaviors] +path = 'crates/vanilla-behaviors' + [workspace.dependencies.indexmap] features = ['rayon'] version = '2.7.1' diff --git a/crates/hyperion-respawn/src/lib.rs b/crates/hyperion-respawn/src/lib.rs index b3c55dbf..4c554a6d 100644 --- a/crates/hyperion-respawn/src/lib.rs +++ b/crates/hyperion-respawn/src/lib.rs @@ -7,17 +7,16 @@ use hyperion::{ }, net::{ConnectionId, DataBundle}, protocol::{ - game_mode::OptGameMode, packets::play::{self, PlayerAbilitiesS2c}, BlockPos, ByteAngle, GlobalPos, VarInt, }, - server::{abilities::PlayerAbilitiesFlags, ident, GameMode}, + server::{abilities::PlayerAbilitiesFlags, ident}, simulation::{ event::{ClientStatusCommand, ClientStatusEvent}, handlers::PacketSwitchQuery, metadata::{entity::Pose, living_entity::Health}, packet::HandlerRegistry, - Flight, FlyingSpeed, Pitch, Position, Uuid, Xp, Yaw, + Flight, FlyingSpeed, Gamemode, Pitch, Position, Uuid, Xp, Yaw, }, }; use hyperion_utils::{EntityExt, LifetimeHandle}; @@ -49,6 +48,7 @@ impl Module for RespawnModule { &Xp, &Flight, &FlyingSpeed, + &Gamemode, )>( |( connection, @@ -61,6 +61,7 @@ impl Module for RespawnModule { xp, flight, flying_speed, + gamemode, )| { health.heal(20.); @@ -77,8 +78,8 @@ impl Module for RespawnModule { dimension_type_name: ident!("minecraft:overworld").into(), dimension_name: ident!("minecraft:overworld").into(), hashed_seed: 0, - game_mode: GameMode::Survival, - previous_game_mode: OptGameMode::default(), + game_mode: gamemode.current, + previous_game_mode: gamemode.previous, is_debug: false, is_flat: false, copy_metadata: false, diff --git a/crates/hyperion/src/egress/player_join/mod.rs b/crates/hyperion/src/egress/player_join/mod.rs index 6251dc4e..79f736f0 100644 --- a/crates/hyperion/src/egress/player_join/mod.rs +++ b/crates/hyperion/src/egress/player_join/mod.rs @@ -8,9 +8,7 @@ use hyperion_utils::EntityExt; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use tracing::{info, instrument}; use valence_protocol::{ - ByteAngle, GameMode, Ident, PacketEncoder, RawBytes, VarInt, Velocity, - game_mode::OptGameMode, - ident, + ByteAngle, Ident, PacketEncoder, RawBytes, VarInt, Velocity, ident, packets::play::{ self, GameJoinS2c, player_position_look_s2c::PlayerPositionLookFlags, @@ -21,7 +19,7 @@ use valence_registry::{BiomeRegistry, RegistryCodec}; use valence_server::entity::EntityKind; use valence_text::IntoText; -use crate::simulation::{MovementTracking, PacketState, Pitch}; +use crate::simulation::{Gamemode, MovementTracking, PacketState, Pitch}; mod list; pub use list::*; @@ -67,9 +65,11 @@ pub fn player_join_world( &Pitch, &PlayerSkin, &EntityFlags, + &Gamemode, )>, crafting_registry: &CraftingRegistry, config: &Config, + gamemode: &Gamemode, ) -> anyhow::Result<()> { static CACHED_DATA: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -111,11 +111,11 @@ pub fn player_join_world( enable_respawn_screen: false, dimension_name: dimension_name.into(), hashed_seed: 0, - game_mode: GameMode::Survival, + game_mode: gamemode.current, is_flat: false, last_death_location: None, portal_cooldown: 60.into(), - previous_game_mode: OptGameMode(Some(GameMode::Survival)), + previous_game_mode: gamemode.previous, dimension_type_name: ident!("minecraft:overworld").into(), is_debug: false, }; @@ -194,7 +194,7 @@ pub fn player_join_world( let _enter = scope.enter(); query .iter_stage(world) - .each(|(uuid, name, _, _, _, _skin, _)| { + .each(|(uuid, name, _, _, _, _skin, _, gamemode)| { // todo: in future, do not clone let entry = PlayerListEntry { @@ -205,7 +205,7 @@ pub fn player_join_world( chat_data: None, listed: true, ping: 20, - game_mode: GameMode::Creative, + game_mode: gamemode.current, display_name: Some(name.to_string().into_cow_text()), }; @@ -240,9 +240,8 @@ pub fn player_join_world( let mut metadata = MetadataChanges::default(); - query - .iter_stage(world) - .each_iter(|it, idx, (uuid, _, position, yaw, pitch, _, flags)| { + query.iter_stage(world).each_iter( + |it, idx, (uuid, _, position, yaw, pitch, _, flags, _)| { let mut result = || { let query_entity = it.entity(idx); @@ -275,7 +274,8 @@ pub fn player_join_world( if let Err(e) = result() { query_errors.push(e); } - }); + }, + ); if !query_errors.is_empty() { return Err(anyhow::anyhow!( @@ -305,7 +305,7 @@ pub fn player_join_world( chat_data: None, listed: true, ping: 20, - game_mode: GameMode::Survival, + game_mode: gamemode.current, display_name: Some(name.to_string().into_cow_text()), }]; @@ -504,6 +504,7 @@ impl Module for PlayerJoinModule { &Pitch, &PlayerSkin, &EntityFlags, + &Gamemode, )>(); let query = SendableQuery(query); @@ -574,8 +575,16 @@ impl Module for PlayerJoinModule { let entity = world.entity_from_id(entity); - entity.get::<(&Uuid, &Name, &Position, &Yaw, &Pitch, &ConnectionId)>( - |(uuid, name, position, yaw, pitch, &stream_id)| { + entity.get::<( + &Uuid, + &Name, + &Position, + &Yaw, + &Pitch, + &ConnectionId, + &Gamemode, + )>( + |(uuid, name, position, yaw, pitch, &stream_id, gamemode)| { let query = &query; let query = &query.0; entity.set_name(name); @@ -597,6 +606,7 @@ impl Module for PlayerJoinModule { query, crafting_registry, config, + gamemode, ) { entity.set(PendingRemove::new(e.to_string())); } diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index 9eed6fa8..bbc0ca4d 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -14,11 +14,14 @@ use uuid; use valence_generated::block::BlockState; use valence_protocol::{ ByteAngle, VarInt, + game_mode::OptGameMode, packets::play::{ - self, PlayerAbilitiesS2c, player_abilities_s2c::PlayerAbilitiesFlags, + self, PlayerAbilitiesS2c, game_state_change_s2c::GameEventKind, + player_abilities_s2c::PlayerAbilitiesFlags, player_position_look_s2c::PlayerPositionLookFlags, }, }; +use valence_server::GameMode; use crate::{ Global, @@ -628,6 +631,21 @@ pub struct Flight { pub is_flying: bool, } +#[derive(Component, Default, Copy, Clone, Debug)] +#[meta] +pub struct LastDamaged { + pub tick: i64, +} + +#[derive(Component, Default, Copy, Clone, Debug)] +#[meta] +/// The Game Mode Component not to confuse with [`GameMode`] from valence +pub struct Gamemode { + pub current: GameMode, + /// Tells the client wich gamemode to select when using the F3+F4 shortcut + pub previous: OptGameMode, +} + #[derive(Component)] pub struct SimModule; @@ -665,6 +683,8 @@ impl Module for SimModule { world.component::(); world.component::(); world.component::().meta(); + world.component::().meta(); + world.component::().meta(); world.component::().meta(); @@ -803,6 +823,12 @@ impl Module for SimModule { world .component::() .add_trait::<(flecs::With, FlyingSpeed)>(); + world + .component::() + .add_trait::<(flecs::With, LastDamaged)>(); + world + .component::() + .add_trait::<(flecs::With, Gamemode)>(); observer!( world, @@ -862,6 +888,22 @@ impl Module for SimModule { compose.unicast(&pkt, *connection, system).unwrap(); }); + + observer!( + world, + flecs::OnSet, &Gamemode, + &Compose($), &ConnectionId + ) + .each_iter(|it, _, (gamemode, compose, connection)| { + let system = it.system(); + + let pkt = play::GameStateChangeS2c { + kind: GameEventKind::ChangeGameMode, + value: (gamemode.current as i8) as f32, + }; + + compose.unicast(&pkt, *connection, system).unwrap(); + }); } } diff --git a/crates/vanilla-behaviors/.gitignore b/crates/vanilla-behaviors/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/crates/vanilla-behaviors/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/vanilla-behaviors/Cargo.toml b/crates/vanilla-behaviors/Cargo.toml new file mode 100644 index 00000000..d0b4eb63 --- /dev/null +++ b/crates/vanilla-behaviors/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "vanilla-behaviors" +version = "0.1.0" +edition = "2021" +authors = ["Andrew Gazelka "] +readme = "README.md" +publish = false + +[dependencies] +hyperion = {workspace = true} +hyperion-clap = { workspace = true } +hyperion-permission = { workspace = true } +hyperion-utils = {workspace = true} +valence_protocol = { workspace = true } +valence_server = { workspace = true } +flecs_ecs = { workspace = true } +fastrand = { workspace = true } +tracing = { workspace = true } +geometry = { workspace = true } +clap = { workspace = true } + +[lints] +workspace = true diff --git a/crates/vanilla-behaviors/README.md b/crates/vanilla-behaviors/README.md new file mode 100644 index 00000000..fa6229c4 --- /dev/null +++ b/crates/vanilla-behaviors/README.md @@ -0,0 +1,3 @@ +# Vanilla Behaviors + +Collection of modules and commands that provide vanilla-like features. \ No newline at end of file diff --git a/crates/vanilla-behaviors/src/command.rs b/crates/vanilla-behaviors/src/command.rs new file mode 100644 index 00000000..78f9e026 --- /dev/null +++ b/crates/vanilla-behaviors/src/command.rs @@ -0,0 +1 @@ +pub mod gamemode; diff --git a/crates/vanilla-behaviors/src/command/gamemode.rs b/crates/vanilla-behaviors/src/command/gamemode.rs new file mode 100644 index 00000000..563c8de2 --- /dev/null +++ b/crates/vanilla-behaviors/src/command/gamemode.rs @@ -0,0 +1,48 @@ +use clap::Parser; +use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider}; +use hyperion::{ + net::{agnostic, Compose, ConnectionId, DataBundle}, + simulation::Gamemode, +}; +use hyperion_clap::{CommandPermission, MinecraftCommand}; +use valence_server::GameMode; + +#[derive(Parser, CommandPermission, Debug)] +#[command(name = "gamemode")] +#[command_permission(group = "Moderator")] +pub struct GamemodeCommand { + #[arg(value_enum)] + mode: hyperion_clap::GameMode, +} + +impl MinecraftCommand for GamemodeCommand { + fn execute(self, system: EntityView<'_>, caller: Entity) { + let world = system.world(); + world.get::<&Compose>(|compose| { + caller + .entity_view(world) + .get::<(&mut Gamemode, &ConnectionId)>(|(gamemode, stream)| { + let new_mode = match self.mode { + hyperion_clap::GameMode::Survival => GameMode::Survival, + hyperion_clap::GameMode::Adventure => GameMode::Adventure, + hyperion_clap::GameMode::Spectator => GameMode::Spectator, + hyperion_clap::GameMode::Creative => GameMode::Creative, + }; + + let chat_packet = if new_mode == gamemode.current { + agnostic::chat("§4Nothing changed") + } else { + agnostic::chat(format!("Changed gamemode to {:?}", new_mode)) + }; + + gamemode.current = new_mode; + caller.entity_view(world).modified::(); + + let mut bundle = DataBundle::new(compose, system); + bundle.add_packet(&chat_packet).unwrap(); + + bundle.unicast(*stream).unwrap(); + }); + }); + } +} diff --git a/crates/vanilla-behaviors/src/lib.rs b/crates/vanilla-behaviors/src/lib.rs new file mode 100644 index 00000000..66382373 --- /dev/null +++ b/crates/vanilla-behaviors/src/lib.rs @@ -0,0 +1,23 @@ +use flecs_ecs::core::EntityView; +use hyperion::{ + flecs_ecs::{self, core::EntityViewGet}, + net::Compose, + simulation::{metadata::living_entity::Health, LastDamaged, Player}, +}; +use tracing::warn; + +pub mod command; +pub mod module; + +pub fn damage_player(entity: &EntityView<'_>, amount: f32, compose: &Compose) { + if entity.has::() { + entity.get::<(&mut Health, &mut LastDamaged)>(|(health, last_damaged)| { + if !health.is_dead() { + health.damage(amount); + last_damaged.tick = compose.global().tick; + } + }); + } else { + warn!("Trying to call a Player only function on an non player entity"); + } +} diff --git a/crates/vanilla-behaviors/src/module.rs b/crates/vanilla-behaviors/src/module.rs new file mode 100644 index 00000000..15f61212 --- /dev/null +++ b/crates/vanilla-behaviors/src/module.rs @@ -0,0 +1 @@ +pub mod natural_damage; diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs new file mode 100644 index 00000000..b07fbaac --- /dev/null +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -0,0 +1,137 @@ +use std::ops::ControlFlow; + +use flecs_ecs::{ + core::{EntityViewGet, QueryBuilderImpl, TermBuilderImpl, World}, + macros::{system, Component}, + prelude::{Module, SystemAPI}, +}; +use geometry::aabb::Aabb; +use hyperion::{ + glam::Vec3, + net::{agnostic, Compose, ConnectionId}, + simulation::{ + aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, EntitySize, Gamemode, Player, + Position, + }, + storage::EventQueue, + BlockKind, +}; +use hyperion_utils::EntityExt; +use valence_protocol::{packets::play, VarInt}; +use valence_server::{ident, GameMode}; + +use crate::damage_player; + +#[derive(Component)] +pub struct NaturalDamageModule {} + +impl Module for NaturalDamageModule { + fn module(world: &World) { + system!("fall damage", world, &mut EventQueue($), &Compose($)).each_iter( + |it, _, (event_queue, compose)| { + let world = it.world(); + let system = it.system(); + + for event in event_queue.drain() { + if event.fall_distance <= 3. { + continue; + } + + let entity = event.client.entity_view(world); + // TODO account for armor/effects + let damage = event.fall_distance.floor() - 3.; + + if damage <= 0. { + continue; + } + + entity.get::<(&ConnectionId, &Position, &Gamemode)>( + |(connection, position, gamemode)| { + if gamemode.current != GameMode::Survival + && gamemode.current != GameMode::Adventure + { + return; + } + + damage_player(&entity, damage, compose); + + let pkt_damage_event = play::EntityDamageS2c { + entity_id: VarInt(entity.minecraft_id()), + source_cause_id: VarInt(0), + source_direct_id: VarInt(0), + source_type_id: VarInt(10), // 10 = fall damage + source_pos: Option::None, + }; + + let sound = agnostic::sound( + if event.fall_distance > 7. { + ident!("minecraft:entity.player.big_fall") + } else { + ident!("minecraft:entity.player.small_fall") + }, + **position, + ) + .volume(1.) + .pitch(1.) + .seed(fastrand::i64(..)) + .build(); + + compose + .unicast(&pkt_damage_event, *connection, system) + .unwrap(); + compose + .broadcast_local(&sound, position.to_chunk(), system) + .send() + .unwrap(); + }, + ); + } + }, + ); + + system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize) + .with::() + .each_iter(|it, row, (compose, blocks, position, size)| { + let world = it.world(); + let system = it.system(); + let entity = it.entity(row); + + let (min, max) = block_bounds(**position, *size); + + let bounding_box = aabb(**position, *size); + + blocks.get_blocks(min, max, |pos, block| { + let pos = Vec3::new(pos.x as f32, pos.y as f32, pos.z as f32); + let kind = block.to_kind(); + + if !is_harmful_block(kind) { + return ControlFlow::Continue(()); + } + + for aabb in block.collision_shapes() { + let aabb = Aabb::new(aabb.min().as_vec3(), aabb.max().as_vec3()); + let aabb = aabb.move_by(pos); + + if bounding_box.collides(&aabb) { + match kind { + BlockKind::Cactus => { + damage_player(&entity, 1., compose); + } + _ => {} + } + return ControlFlow::Break(false); + } + } + + ControlFlow::Continue(()) + }); + }); + } +} + +pub fn is_harmful_block(kind: BlockKind) -> bool { + matches!( + kind, + BlockKind::Lava | BlockKind::Cactus | BlockKind::MagmaBlock | BlockKind::SweetBerryBush + ) +} diff --git a/events/tag/Cargo.toml b/events/tag/Cargo.toml index 1463ef23..3caaaa0c 100644 --- a/events/tag/Cargo.toml +++ b/events/tag/Cargo.toml @@ -31,6 +31,7 @@ valence_protocol = { workspace = true } valence_server = { workspace = true } serde = { version = "1.0", features = ["derive"] } envy = "0.4" +vanilla-behaviors = { workspace = true } [dev-dependencies] tracing = { workspace = true, features = ["release_max_level_info"] } diff --git a/events/tag/src/command.rs b/events/tag/src/command.rs index a11d353e..8312ac11 100644 --- a/events/tag/src/command.rs +++ b/events/tag/src/command.rs @@ -1,5 +1,6 @@ use flecs_ecs::core::World; use hyperion_clap::{MinecraftCommand, hyperion_command::CommandRegistry}; +use vanilla_behaviors::command::gamemode::GamemodeCommand; use crate::command::{ bow::BowCommand, chest::ChestCommand, class::ClassCommand, fly::FlyCommand, gui::GuiCommand, @@ -33,4 +34,5 @@ pub fn register(registry: &mut CommandRegistry, world: &World) { VanishCommand::register(registry, world); XpCommand::register(registry, world); ChestCommand::register(registry, world); + GamemodeCommand::register(registry, world); } diff --git a/events/tag/src/lib.rs b/events/tag/src/lib.rs index 647420ed..b2c4eddc 100644 --- a/events/tag/src/lib.rs +++ b/events/tag/src/lib.rs @@ -9,7 +9,7 @@ use flecs_ecs::prelude::*; use hyperion::{GameServerEndpoint, HyperionCore, simulation::Player}; use hyperion_clap::hyperion_command::CommandRegistry; use hyperion_gui::Gui; -use module::{block::BlockModule, damage::DamageModule, vanish::VanishModule}; +use module::{block::BlockModule, vanish::VanishModule}; mod module; @@ -18,6 +18,7 @@ use hyperion::{glam::IVec3, simulation::Position, spatial}; use hyperion_rank_tree::Team; use module::{attack::AttackModule, level::LevelModule, regeneration::RegenerationModule}; use spatial::SpatialIndex; +use vanilla_behaviors::module::natural_damage::NaturalDamageModule; use crate::{ module::{bow::BowModule, chat::ChatModule, spawn::SpawnModule, stats::StatsModule}, @@ -83,7 +84,7 @@ impl Module for TagModule { world.import::(); world.import::(); world.import::(); - world.import::(); + world.import::(); world.get::<&mut CommandRegistry>(|registry| { command::register(registry, world); diff --git a/events/tag/src/module.rs b/events/tag/src/module.rs index 880f99b9..4f940e26 100644 --- a/events/tag/src/module.rs +++ b/events/tag/src/module.rs @@ -2,7 +2,6 @@ pub mod attack; pub mod block; pub mod bow; pub mod chat; -pub mod damage; pub mod level; pub mod regeneration; pub mod spawn; diff --git a/events/tag/src/module/damage.rs b/events/tag/src/module/damage.rs deleted file mode 100644 index 296413a6..00000000 --- a/events/tag/src/module/damage.rs +++ /dev/null @@ -1,75 +0,0 @@ -use flecs_ecs::{ - core::{EntityViewGet, QueryBuilderImpl, TermBuilderImpl, World}, - macros::{Component, system}, - prelude::{Module, SystemAPI}, -}; -use hyperion::{ - net::{Compose, ConnectionId, agnostic}, - simulation::{Position, event::HitGroundEvent, metadata::living_entity::Health}, - storage::EventQueue, -}; -use hyperion_utils::EntityExt; -use valence_protocol::{VarInt, packets::play}; -use valence_server::ident; - -#[derive(Component)] -pub struct DamageModule {} - -impl Module for DamageModule { - fn module(world: &World) { - system!("apply natural damages", world, &mut EventQueue($), &Compose($)) - .each_iter(|it, _, (event_queue, compose)| { - let world = it.world(); - let system = it.system(); - - for event in event_queue.drain() { - if event.fall_distance <= 3. { - continue; - } - - let entity = event.client.entity_view(world); - // TODO account for armor/effects and gamemode - let damage = event.fall_distance.floor() - 3.; - - if damage <= 0. { - continue; - } - - entity.get::<(&mut Health, &ConnectionId, &Position)>( - |(health, connection, position)| { - health.damage(damage); - - let pkt_damage_event = play::EntityDamageS2c { - entity_id: VarInt(entity.minecraft_id()), - source_cause_id: VarInt(0), - source_direct_id: VarInt(0), - source_type_id: VarInt(10), // 10 = fall damage - source_pos: Option::None, - }; - - let sound = agnostic::sound( - if event.fall_distance > 7. { - ident!("minecraft:entity.player.big_fall") - } else { - ident!("minecraft:entity.player.small_fall") - }, - **position, - ) - .volume(1.) - .pitch(1.) - .seed(fastrand::i64(..)) - .build(); - - compose - .unicast(&pkt_damage_event, *connection, system) - .unwrap(); - compose - .broadcast_local(&sound, position.to_chunk(), system) - .send() - .unwrap(); - }, - ); - } - }); - } -} diff --git a/events/tag/src/module/regeneration.rs b/events/tag/src/module/regeneration.rs index c76a11e7..c2b9c908 100644 --- a/events/tag/src/module/regeneration.rs +++ b/events/tag/src/module/regeneration.rs @@ -1,12 +1,12 @@ use flecs_ecs::{ - core::{ComponentOrPairId, QueryBuilderImpl, TermBuilderImpl, World, flecs}, + core::{QueryBuilderImpl, TermBuilderImpl, World}, macros::{Component, system}, prelude::Module, }; use hyperion::{ Prev, net::Compose, - simulation::{Player, metadata::living_entity::Health}, + simulation::{LastDamaged, metadata::living_entity::Health}, util::TracingExt, }; use tracing::info_span; @@ -14,23 +14,11 @@ use tracing::info_span; #[derive(Component)] pub struct RegenerationModule; -#[derive(Component, Default, Copy, Clone, Debug)] -#[meta] -pub struct LastDamaged { - pub tick: i64, -} - const MAX_HEALTH: f32 = 20.0; impl Module for RegenerationModule { #[allow(clippy::excessive_nesting)] fn module(world: &World) { - world.component::().meta(); - - world - .component::() - .add_trait::<(flecs::With, LastDamaged)>(); // todo: how does this even call Default? (IndraDb) - system!( "regenerate", world, From f2269934a10954b08c341adf52f8b9154c4fe286 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Tue, 4 Mar 2025 21:16:08 +0100 Subject: [PATCH 02/13] feat: cactus and in fire damage --- crates/hyperion/src/egress/player_join/mod.rs | 1 + crates/hyperion/src/simulation/mod.rs | 2 +- .../vanilla-behaviors/src/command/gamemode.rs | 2 +- crates/vanilla-behaviors/src/lib.rs | 126 ++++++++++++++++-- .../src/module/natural_damage.rs | 108 +++++++-------- 5 files changed, 177 insertions(+), 62 deletions(-) diff --git a/crates/hyperion/src/egress/player_join/mod.rs b/crates/hyperion/src/egress/player_join/mod.rs index 79f736f0..1fbc9a18 100644 --- a/crates/hyperion/src/egress/player_join/mod.rs +++ b/crates/hyperion/src/egress/player_join/mod.rs @@ -44,6 +44,7 @@ use crate::{ reason = "todo: we should refactor at some point" )] #[instrument(skip_all, fields(name = name))] +#[allow(clippy::type_complexity)] pub fn player_join_world( entity: &EntityView<'_>, compose: &Compose, diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index bbc0ca4d..7119db81 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -899,7 +899,7 @@ impl Module for SimModule { let pkt = play::GameStateChangeS2c { kind: GameEventKind::ChangeGameMode, - value: (gamemode.current as i8) as f32, + value: f32::from(gamemode.current as i8), }; compose.unicast(&pkt, *connection, system).unwrap(); diff --git a/crates/vanilla-behaviors/src/command/gamemode.rs b/crates/vanilla-behaviors/src/command/gamemode.rs index 563c8de2..48b8a969 100644 --- a/crates/vanilla-behaviors/src/command/gamemode.rs +++ b/crates/vanilla-behaviors/src/command/gamemode.rs @@ -32,7 +32,7 @@ impl MinecraftCommand for GamemodeCommand { let chat_packet = if new_mode == gamemode.current { agnostic::chat("§4Nothing changed") } else { - agnostic::chat(format!("Changed gamemode to {:?}", new_mode)) + agnostic::chat(format!("Changed gamemode to {new_mode:?}")) }; gamemode.current = new_mode; diff --git a/crates/vanilla-behaviors/src/lib.rs b/crates/vanilla-behaviors/src/lib.rs index 66382373..9f9b5e71 100644 --- a/crates/vanilla-behaviors/src/lib.rs +++ b/crates/vanilla-behaviors/src/lib.rs @@ -1,23 +1,133 @@ use flecs_ecs::core::EntityView; use hyperion::{ flecs_ecs::{self, core::EntityViewGet}, - net::Compose, + glam::DVec3, + net::{Compose, ConnectionId}, simulation::{metadata::living_entity::Health, LastDamaged, Player}, }; +use hyperion_utils::EntityExt; use tracing::warn; +use valence_protocol::{packets::play, VarInt}; +use valence_server::GameMode; pub mod command; pub mod module; -pub fn damage_player(entity: &EntityView<'_>, amount: f32, compose: &Compose) { +// /!\ Minecraft version dependent +pub enum DamageType { + InFire, + LightningBolt, + OnFire, + Lava, + HotFloor, + InWall, + Cramming, + Drown, + Starve, + Cactus, + Fall, + FlyIntoWall, + FellOutOfWorld, + Generic, + Magic, + Wither, + DragonBreath, + DryOut, + SweetBerryBush, + Freeze, + Stalagmite, + FallingBlock, + FallingAnvil, + FallingStalactite, + Sting, + MobAttack, + MobAttackNoAggro, + PlayerAttack, + Arrow, + Trident, + MobProjectile, + Fireworks, + UnattributedFireball, + Fireball, + WitherSkull, + Thrown, + IndirectMagic, + Thorns, + Explosion, + PlayerExplosion, + SonicBoom, + BadRespawnPoint, + OutsideBorder, + GenericKill, +} + +pub struct DamageCause { + pub damage_type: DamageType, + pub position: Option, + pub source_entity: i32, + pub direct_source: i32, +} + +impl DamageCause { + #[must_use] + pub const fn new(damage_type: DamageType) -> Self { + Self { + damage_type, + position: Option::None, + source_entity: 0, + direct_source: 0, + } + } + + pub const fn with_position(&mut self, position: DVec3) -> &mut Self { + self.position = Option::Some(position); + self + } + + pub const fn with_entities(&mut self, source: i32, direct_source: i32) -> &mut Self { + self.source_entity = source; + self.direct_source = direct_source; + self + } +} + +#[must_use] +pub const fn is_invincible(gamemode: &GameMode) -> bool { + matches!(gamemode, GameMode::Creative | GameMode::Spectator) +} + +pub fn damage_player( + entity: &EntityView<'_>, + amount: f32, + damage_cause: DamageCause, + compose: &Compose, + system: EntityView<'_>, +) -> bool { if entity.has::() { - entity.get::<(&mut Health, &mut LastDamaged)>(|(health, last_damaged)| { - if !health.is_dead() { - health.damage(amount); - last_damaged.tick = compose.global().tick; - } - }); + entity.get::<(&mut Health, &mut LastDamaged, &ConnectionId)>( + |(health, last_damaged, connection)| { + if !health.is_dead() && compose.global().tick - last_damaged.tick > 20 { + health.damage(amount); + last_damaged.tick = compose.global().tick; + + let pkt_damage_event = play::EntityDamageS2c { + entity_id: VarInt(entity.minecraft_id()), + source_cause_id: VarInt(damage_cause.source_entity), + source_direct_id: VarInt(damage_cause.direct_source), + source_type_id: VarInt(damage_cause.damage_type as i32), + source_pos: damage_cause.position, + }; + + compose + .unicast(&pkt_damage_event, *connection, system) + .unwrap(); // Should brodcast locally? + return true; + } + false + }, + ) } else { warn!("Trying to call a Player only function on an non player entity"); + false } } diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index b07fbaac..5185b192 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -8,7 +8,7 @@ use flecs_ecs::{ use geometry::aabb::Aabb; use hyperion::{ glam::Vec3, - net::{agnostic, Compose, ConnectionId}, + net::{agnostic, Compose}, simulation::{ aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, EntitySize, Gamemode, Player, Position, @@ -16,11 +16,9 @@ use hyperion::{ storage::EventQueue, BlockKind, }; -use hyperion_utils::EntityExt; -use valence_protocol::{packets::play, VarInt}; -use valence_server::{ident, GameMode}; +use valence_server::ident; -use crate::damage_player; +use crate::{damage_player, is_invincible, DamageCause, DamageType}; #[derive(Component)] pub struct NaturalDamageModule {} @@ -45,59 +43,52 @@ impl Module for NaturalDamageModule { continue; } - entity.get::<(&ConnectionId, &Position, &Gamemode)>( - |(connection, position, gamemode)| { - if gamemode.current != GameMode::Survival - && gamemode.current != GameMode::Adventure - { - return; - } + entity.get::<(&Position, &Gamemode)>(|(position, gamemode)| { + if is_invincible(&gamemode.current) { + return; + } - damage_player(&entity, damage, compose); - - let pkt_damage_event = play::EntityDamageS2c { - entity_id: VarInt(entity.minecraft_id()), - source_cause_id: VarInt(0), - source_direct_id: VarInt(0), - source_type_id: VarInt(10), // 10 = fall damage - source_pos: Option::None, - }; - - let sound = agnostic::sound( - if event.fall_distance > 7. { - ident!("minecraft:entity.player.big_fall") - } else { - ident!("minecraft:entity.player.small_fall") - }, - **position, - ) - .volume(1.) - .pitch(1.) - .seed(fastrand::i64(..)) - .build(); - - compose - .unicast(&pkt_damage_event, *connection, system) - .unwrap(); - compose - .broadcast_local(&sound, position.to_chunk(), system) - .send() - .unwrap(); - }, - ); + damage_player( + &entity, + damage, + DamageCause::new(DamageType::Fall), + compose, + system, + ); + + let sound = agnostic::sound( + if event.fall_distance > 7. { + ident!("minecraft:entity.player.big_fall") + } else { + ident!("minecraft:entity.player.small_fall") + }, + **position, + ) + .volume(1.) + .pitch(1.) + .seed(fastrand::i64(..)) + .build(); + + compose + .broadcast_local(&sound, position.to_chunk(), system) + .send() + .unwrap(); + }); } }, ); - system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize) + system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode) .with::() - .each_iter(|it, row, (compose, blocks, position, size)| { - let world = it.world(); + .each_iter(|it, row, (compose, blocks, position, size, gamemode)| { + if is_invincible(&gamemode.current) { + return; + } + let system = it.system(); let entity = it.entity(row); let (min, max) = block_bounds(**position, *size); - let bounding_box = aabb(**position, *size); blocks.get_blocks(min, max, |pos, block| { @@ -115,11 +106,18 @@ impl Module for NaturalDamageModule { if bounding_box.collides(&aabb) { match kind { BlockKind::Cactus => { - damage_player(&entity, 1., compose); + damage_player(&entity, 1., DamageCause::new(DamageType::Cactus), compose, system); + } + BlockKind::Fire => { + // Todo check for overlap not just collision + damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); + } + BlockKind::SoulFire => { + damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); } _ => {} } - return ControlFlow::Break(false); + return ControlFlow::Break(()); } } @@ -129,9 +127,15 @@ impl Module for NaturalDamageModule { } } -pub fn is_harmful_block(kind: BlockKind) -> bool { +#[must_use] +pub const fn is_harmful_block(kind: BlockKind) -> bool { matches!( kind, - BlockKind::Lava | BlockKind::Cactus | BlockKind::MagmaBlock | BlockKind::SweetBerryBush + BlockKind::Lava + | BlockKind::Cactus + | BlockKind::MagmaBlock + | BlockKind::SweetBerryBush + | BlockKind::SoulFire + | BlockKind::Fire ) } From 338a7a0447fcaadc1173b4ec222d5a9310245aca Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Wed, 5 Mar 2025 00:37:35 +0100 Subject: [PATCH 03/13] feat: magma block damage --- .../src/module/natural_damage.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 5185b192..3a95a8f4 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -78,6 +78,7 @@ impl Module for NaturalDamageModule { }, ); + #[allow(clippy::excessive_nesting)] system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode) .with::() .each_iter(|it, row, (compose, blocks, position, size, gamemode)| { @@ -89,6 +90,7 @@ impl Module for NaturalDamageModule { let entity = it.entity(row); let (min, max) = block_bounds(**position, *size); + let min = min.with_y(min.y-1); let bounding_box = aabb(**position, *size); blocks.get_blocks(min, max, |pos, block| { @@ -108,12 +110,10 @@ impl Module for NaturalDamageModule { BlockKind::Cactus => { damage_player(&entity, 1., DamageCause::new(DamageType::Cactus), compose, system); } - BlockKind::Fire => { - // Todo check for overlap not just collision - damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); - } - BlockKind::SoulFire => { - damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); + BlockKind::MagmaBlock => { + if position.y > pos.y { + damage_player(&entity, 1., DamageCause::new(DamageType::HotFloor), compose, system); + } } _ => {} } @@ -121,6 +121,19 @@ impl Module for NaturalDamageModule { } } + let aabb = Aabb::new(Vec3::ZERO, Vec3::ONE).move_by(pos); + + if Aabb::overlap(&aabb, &bounding_box).is_some() { + match kind { + BlockKind::Fire => { + damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); + } + BlockKind::SoulFire => { + damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); + } + _ => {} + } + } ControlFlow::Continue(()) }); }); From 28bebca09c8fcdfd3d8ca93f18da4ddb951fbc93 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Wed, 5 Mar 2025 11:29:57 +0100 Subject: [PATCH 04/13] feat: sweet berry bush damage --- .../src/module/natural_damage.rs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 3a95a8f4..615f7234 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -10,13 +10,16 @@ use hyperion::{ glam::Vec3, net::{agnostic, Compose}, simulation::{ - aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, EntitySize, Gamemode, Player, - Position, + aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, EntitySize, Gamemode, + MovementTracking, Player, Position, }, storage::EventQueue, BlockKind, }; -use valence_server::ident; +use valence_server::{ + block::{PropName, PropValue}, + ident, +}; use crate::{damage_player, is_invincible, DamageCause, DamageType}; @@ -79,9 +82,9 @@ impl Module for NaturalDamageModule { ); #[allow(clippy::excessive_nesting)] - system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode) + system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode, &MovementTracking) .with::() - .each_iter(|it, row, (compose, blocks, position, size, gamemode)| { + .each_iter(|it, row, (compose, blocks, position, size, gamemode, movement)| { if is_invincible(&gamemode.current) { return; } @@ -131,6 +134,16 @@ impl Module for NaturalDamageModule { BlockKind::SoulFire => { damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); } + BlockKind::SweetBerryBush => { + let grown = block.get(PropName::Age) + .is_some_and(|x| x != PropValue::_0); + let delta_x = (f64::from(position.x) - f64::from(movement.last_tick_position.x)).abs(); + let delta_y = (f64::from(position.y) - f64::from(movement.last_tick_position.y)).abs(); + + if grown && delta_x >= 0.003_000_000_026_077_032 && delta_y >= 0.003_000_000_026_077_032 && movement.last_tick_position != **position { + damage_player(&entity, 1., DamageCause::new(DamageType::SweetBerryBush), compose, system); + } + } _ => {} } } From 652a0863225781b7ab0296e0af5db5dd571e05ba Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Wed, 5 Mar 2025 12:00:55 +0100 Subject: [PATCH 05/13] move respawn module to vanilla-behaviors --- Cargo.lock | 9 --------- Cargo.toml | 4 ---- crates/hyperion-respawn/.gitignore | 1 - crates/hyperion-respawn/Cargo.toml | 14 -------------- crates/hyperion-respawn/README.md | 1 - crates/vanilla-behaviors/src/module.rs | 1 + .../src/module/respawn.rs} | 0 events/tag/Cargo.toml | 1 - events/tag/src/lib.rs | 4 ++-- 9 files changed, 3 insertions(+), 32 deletions(-) delete mode 100644 crates/hyperion-respawn/.gitignore delete mode 100644 crates/hyperion-respawn/Cargo.toml delete mode 100644 crates/hyperion-respawn/README.md rename crates/{hyperion-respawn/src/lib.rs => vanilla-behaviors/src/module/respawn.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index f0c99412..83b0164a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3035,14 +3035,6 @@ dependencies = [ "valence_protocol", ] -[[package]] -name = "hyperion-respawn" -version = "0.1.0" -dependencies = [ - "hyperion", - "hyperion-utils", -] - [[package]] name = "hyperion-scheduled" version = "0.1.0" @@ -5732,7 +5724,6 @@ dependencies = [ "hyperion-item", "hyperion-permission", "hyperion-rank-tree", - "hyperion-respawn", "hyperion-scheduled", "hyperion-text", "hyperion-utils", diff --git a/Cargo.toml b/Cargo.toml index 63fe4764..65112f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ members = [ 'crates/hyperion-proto', 'crates/hyperion-proxy', 'crates/hyperion-rank-tree', - 'crates/hyperion-respawn', 'crates/hyperion-scheduled', 'crates/hyperion-stats', 'crates/hyperion-text', @@ -223,9 +222,6 @@ version = '0.3.6' features = ['rustls-tls', 'stream'] version = '0.12.12' -[workspace.dependencies.hyperion-respawn] -path = 'crates/hyperion-respawn' - [workspace.dependencies.roaring] features = ['simd'] version = '0.10.10' diff --git a/crates/hyperion-respawn/.gitignore b/crates/hyperion-respawn/.gitignore deleted file mode 100644 index ea8c4bf7..00000000 --- a/crates/hyperion-respawn/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/crates/hyperion-respawn/Cargo.toml b/crates/hyperion-respawn/Cargo.toml deleted file mode 100644 index 2d233386..00000000 --- a/crates/hyperion-respawn/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "hyperion-respawn" -version = "0.1.0" -edition = "2021" -authors = ["Andrew Gazelka "] -readme = "README.md" -publish = false - -[dependencies] -hyperion = {workspace = true} -hyperion-utils = {workspace = true} - -[lints] -workspace = true diff --git a/crates/hyperion-respawn/README.md b/crates/hyperion-respawn/README.md deleted file mode 100644 index cb2e8806..00000000 --- a/crates/hyperion-respawn/README.md +++ /dev/null @@ -1 +0,0 @@ -# respawn \ No newline at end of file diff --git a/crates/vanilla-behaviors/src/module.rs b/crates/vanilla-behaviors/src/module.rs index 15f61212..e515e748 100644 --- a/crates/vanilla-behaviors/src/module.rs +++ b/crates/vanilla-behaviors/src/module.rs @@ -1 +1,2 @@ pub mod natural_damage; +pub mod respawn; diff --git a/crates/hyperion-respawn/src/lib.rs b/crates/vanilla-behaviors/src/module/respawn.rs similarity index 100% rename from crates/hyperion-respawn/src/lib.rs rename to crates/vanilla-behaviors/src/module/respawn.rs diff --git a/events/tag/Cargo.toml b/events/tag/Cargo.toml index 3caaaa0c..114ef78d 100644 --- a/events/tag/Cargo.toml +++ b/events/tag/Cargo.toml @@ -26,7 +26,6 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } # tracing-tracy = { workspace = true } uuid = { workspace = true } -hyperion-respawn = { workspace = true } valence_protocol = { workspace = true } valence_server = { workspace = true } serde = { version = "1.0", features = ["derive"] } diff --git a/events/tag/src/lib.rs b/events/tag/src/lib.rs index b2c4eddc..ecb48b57 100644 --- a/events/tag/src/lib.rs +++ b/events/tag/src/lib.rs @@ -18,7 +18,7 @@ use hyperion::{glam::IVec3, simulation::Position, spatial}; use hyperion_rank_tree::Team; use module::{attack::AttackModule, level::LevelModule, regeneration::RegenerationModule}; use spatial::SpatialIndex; -use vanilla_behaviors::module::natural_damage::NaturalDamageModule; +use vanilla_behaviors::module::{natural_damage::NaturalDamageModule, respawn::RespawnModule}; use crate::{ module::{bow::BowModule, chat::ChatModule, spawn::SpawnModule, stats::StatsModule}, @@ -73,7 +73,7 @@ impl Module for TagModule { world.import::(); world.import::(); world.import::(); - world.import::(); + world.import::(); world.import::(); world.import::(); world.import::(); From cc09a8cc18f64b158b92083b2dc1095586d95ba2 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Wed, 5 Mar 2025 17:00:24 +0100 Subject: [PATCH 06/13] wip burning damage --- crates/hyperion/src/simulation/mod.rs | 19 +++++++++++++- .../src/module/natural_damage.rs | 26 ++++++++++++++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index 7119db81..b2ecf2a4 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -646,6 +646,19 @@ pub struct Gamemode { pub previous: OptGameMode, } +#[derive(Component, Default, Copy, Clone, Debug)] +#[meta] +pub struct BurningState { + pub immune: bool, + pub fire_ticks_left: u16, +} + +impl BurningState { + pub const fn burn_for_seconds(&mut self, seconds: u16) { + self.fire_ticks_left = 20 * seconds; + } +} + #[derive(Component)] pub struct SimModule; @@ -684,7 +697,8 @@ impl Module for SimModule { world.component::(); world.component::().meta(); world.component::().meta(); - world.component::().meta(); + world.component::(); + world.component::().meta(); world.component::().meta(); @@ -829,6 +843,9 @@ impl Module for SimModule { world .component::() .add_trait::<(flecs::With, Gamemode)>(); + world + .component::() + .add_trait::<(flecs::With, BurningState)>(); observer!( world, diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 615f7234..88785e6a 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -10,8 +10,8 @@ use hyperion::{ glam::Vec3, net::{agnostic, Compose}, simulation::{ - aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, EntitySize, Gamemode, - MovementTracking, Player, Position, + aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, BurningState, EntitySize, + Gamemode, MovementTracking, Player, Position, }, storage::EventQueue, BlockKind, @@ -82,9 +82,9 @@ impl Module for NaturalDamageModule { ); #[allow(clippy::excessive_nesting)] - system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode, &MovementTracking) + system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode, &MovementTracking, &mut BurningState) .with::() - .each_iter(|it, row, (compose, blocks, position, size, gamemode, movement)| { + .each_iter(|it, row, (compose, blocks, position, size, gamemode, movement, burning)| { if is_invincible(&gamemode.current) { return; } @@ -114,7 +114,7 @@ impl Module for NaturalDamageModule { damage_player(&entity, 1., DamageCause::new(DamageType::Cactus), compose, system); } BlockKind::MagmaBlock => { - if position.y > pos.y { + if position.y > pos.y && !burning.immune{ damage_player(&entity, 1., DamageCause::new(DamageType::HotFloor), compose, system); } } @@ -129,10 +129,22 @@ impl Module for NaturalDamageModule { if Aabb::overlap(&aabb, &bounding_box).is_some() { match kind { BlockKind::Fire => { - damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); + if !burning.immune { + damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); + + } } BlockKind::SoulFire => { - damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); + if !burning.immune { + damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); + } + } + BlockKind::Lava => { + if !burning.immune { + damage_player(&entity, 4., DamageCause::new(DamageType::InFire), compose, system); + burning.burn_for_seconds(15); + // this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F); + } } BlockKind::SweetBerryBush => { let grown = block.get(PropName::Age) From c10940f6e47e54758502eacca646c2c4e09a4162 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Wed, 5 Mar 2025 18:35:52 +0100 Subject: [PATCH 07/13] on fire damage --- crates/vanilla-behaviors/src/lib.rs | 2 +- .../src/module/natural_damage.rs | 25 +++++++++++++++---- .../vanilla-behaviors/src/module/respawn.rs | 5 +++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/vanilla-behaviors/src/lib.rs b/crates/vanilla-behaviors/src/lib.rs index 9f9b5e71..34b0f342 100644 --- a/crates/vanilla-behaviors/src/lib.rs +++ b/crates/vanilla-behaviors/src/lib.rs @@ -106,7 +106,7 @@ pub fn damage_player( if entity.has::() { entity.get::<(&mut Health, &mut LastDamaged, &ConnectionId)>( |(health, last_damaged, connection)| { - if !health.is_dead() && compose.global().tick - last_damaged.tick > 20 { + if !health.is_dead() && compose.global().tick - last_damaged.tick >= 20 { health.damage(amount); last_damaged.tick = compose.global().tick; diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 88785e6a..71b20970 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -10,8 +10,8 @@ use hyperion::{ glam::Vec3, net::{agnostic, Compose}, simulation::{ - aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, BurningState, EntitySize, - Gamemode, MovementTracking, Player, Position, + aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, metadata::entity::EntityFlags, + BurningState, EntitySize, Gamemode, MovementTracking, Player, Position, }, storage::EventQueue, BlockKind, @@ -82,9 +82,9 @@ impl Module for NaturalDamageModule { ); #[allow(clippy::excessive_nesting)] - system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode, &MovementTracking, &mut BurningState) + system!("natural block damage", world, &Compose($), &Blocks($), &Position, &EntitySize, &Gamemode, &MovementTracking, &mut BurningState, &EntityFlags) .with::() - .each_iter(|it, row, (compose, blocks, position, size, gamemode, movement, burning)| { + .each_iter(|it, row, (compose, blocks, position, size, gamemode, movement, burning, flags)| { if is_invincible(&gamemode.current) { return; } @@ -131,12 +131,13 @@ impl Module for NaturalDamageModule { BlockKind::Fire => { if !burning.immune { damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); - + burning.burn_for_seconds(8); } } BlockKind::SoulFire => { if !burning.immune { damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); + burning.burn_for_seconds(8); } } BlockKind::Lava => { @@ -161,6 +162,20 @@ impl Module for NaturalDamageModule { } ControlFlow::Continue(()) }); + + // TODO vanilla doesnt put the player on fire if they dont stay in fire longer than a certain threshold + if burning.fire_ticks_left > 0 { + if !burning.immune && burning.fire_ticks_left % 20 == 0 { + damage_player(&entity, 1., DamageCause::new(DamageType::OnFire), compose, system); + } + burning.fire_ticks_left -= 1; + } + + if burning.fire_ticks_left > 0 { + entity.set(*flags | EntityFlags::ON_FIRE); + } else { + entity.set(*flags & !EntityFlags::ON_FIRE); + } }); } } diff --git a/crates/vanilla-behaviors/src/module/respawn.rs b/crates/vanilla-behaviors/src/module/respawn.rs index 4c554a6d..2a6a75db 100644 --- a/crates/vanilla-behaviors/src/module/respawn.rs +++ b/crates/vanilla-behaviors/src/module/respawn.rs @@ -16,7 +16,7 @@ use hyperion::{ handlers::PacketSwitchQuery, metadata::{entity::Pose, living_entity::Health}, packet::HandlerRegistry, - Flight, FlyingSpeed, Gamemode, Pitch, Position, Uuid, Xp, Yaw, + BurningState, Flight, FlyingSpeed, Gamemode, Pitch, Position, Uuid, Xp, Yaw, }, }; use hyperion_utils::{EntityExt, LifetimeHandle}; @@ -49,6 +49,7 @@ impl Module for RespawnModule { &Flight, &FlyingSpeed, &Gamemode, + &mut BurningState, )>( |( connection, @@ -62,8 +63,10 @@ impl Module for RespawnModule { flight, flying_speed, gamemode, + burning, )| { health.heal(20.); + burning.fire_ticks_left = 0; *pose = Pose::Standing; client.modified::(); // this is so observers detect the change From b89142745eaec94e3a1e41cd746d7c81a2a5d1b5 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Thu, 6 Mar 2025 00:26:07 +0100 Subject: [PATCH 08/13] small fix to burn_for_seconds() --- crates/hyperion/src/simulation/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index b2ecf2a4..e737cf87 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -655,7 +655,10 @@ pub struct BurningState { impl BurningState { pub const fn burn_for_seconds(&mut self, seconds: u16) { - self.fire_ticks_left = 20 * seconds; + let duration = 20 * seconds; + if duration > self.fire_ticks_left { + self.fire_ticks_left = duration; + } } } From f6e491d3d890b38ff216f650c69c80863a418816 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Thu, 6 Mar 2025 21:51:02 +0100 Subject: [PATCH 09/13] fixes --- crates/hyperion-utils/src/lib.rs | 1 + crates/hyperion-utils/src/structures.rs | 79 ++++++++++++ crates/hyperion/src/simulation/mod.rs | 8 +- crates/vanilla-behaviors/src/lib.rs | 120 ++++-------------- .../src/module/natural_damage.rs | 94 +++++++++++--- 5 files changed, 190 insertions(+), 112 deletions(-) create mode 100644 crates/hyperion-utils/src/structures.rs diff --git a/crates/hyperion-utils/src/lib.rs b/crates/hyperion-utils/src/lib.rs index 35237b4d..f13a0a66 100644 --- a/crates/hyperion-utils/src/lib.rs +++ b/crates/hyperion-utils/src/lib.rs @@ -6,6 +6,7 @@ use flecs_ecs::{ mod cached_save; mod lifetime; +pub mod structures; pub use cached_save::cached_save; pub use lifetime::*; diff --git a/crates/hyperion-utils/src/structures.rs b/crates/hyperion-utils/src/structures.rs new file mode 100644 index 00000000..e64aab54 --- /dev/null +++ b/crates/hyperion-utils/src/structures.rs @@ -0,0 +1,79 @@ +use valence_protocol::math::DVec3; + +// /!\ Minecraft version dependent +pub enum DamageType { + InFire, + LightningBolt, + OnFire, + Lava, + HotFloor, + InWall, + Cramming, + Drown, + Starve, + Cactus, + Fall, + FlyIntoWall, + FellOutOfWorld, + Generic, + Magic, + Wither, + DragonBreath, + DryOut, + SweetBerryBush, + Freeze, + Stalagmite, + FallingBlock, + FallingAnvil, + FallingStalactite, + Sting, + MobAttack, + MobAttackNoAggro, + PlayerAttack, + Arrow, + Trident, + MobProjectile, + Fireworks, + UnattributedFireball, + Fireball, + WitherSkull, + Thrown, + IndirectMagic, + Thorns, + Explosion, + PlayerExplosion, + SonicBoom, + BadRespawnPoint, + OutsideBorder, + GenericKill, +} + +pub struct DamageCause { + pub damage_type: DamageType, + pub position: Option, + pub source_entity: i32, + pub direct_source: i32, +} + +impl DamageCause { + #[must_use] + pub const fn new(damage_type: DamageType) -> Self { + Self { + damage_type, + position: Option::None, + source_entity: 0, + direct_source: 0, + } + } + + pub const fn with_position(&mut self, position: DVec3) -> &mut Self { + self.position = Option::Some(position); + self + } + + pub const fn with_entities(&mut self, source: i32, direct_source: i32) -> &mut Self { + self.source_entity = source; + self.direct_source = direct_source; + self + } +} diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index e737cf87..b02a8a53 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -635,6 +635,8 @@ pub struct Flight { #[meta] pub struct LastDamaged { pub tick: i64, + /// The amount of inflicted damages + pub amount: f32, } #[derive(Component, Default, Copy, Clone, Debug)] @@ -650,11 +652,13 @@ pub struct Gamemode { #[meta] pub struct BurningState { pub immune: bool, - pub fire_ticks_left: u16, + pub fire_ticks_left: i32, + /// TODO move to `MovementTracker` as it is also useful to calculate fall damage and velocity + pub in_lava: bool, } impl BurningState { - pub const fn burn_for_seconds(&mut self, seconds: u16) { + pub const fn burn_for_seconds(&mut self, seconds: i32) { let duration = 20 * seconds; if duration > self.fire_ticks_left { self.fire_ticks_left = duration; diff --git a/crates/vanilla-behaviors/src/lib.rs b/crates/vanilla-behaviors/src/lib.rs index 34b0f342..0a2dd371 100644 --- a/crates/vanilla-behaviors/src/lib.rs +++ b/crates/vanilla-behaviors/src/lib.rs @@ -1,11 +1,10 @@ use flecs_ecs::core::EntityView; use hyperion::{ flecs_ecs::{self, core::EntityViewGet}, - glam::DVec3, - net::{Compose, ConnectionId}, - simulation::{metadata::living_entity::Health, LastDamaged, Player}, + net::Compose, + simulation::{metadata::living_entity::Health, LastDamaged, Player, Position}, }; -use hyperion_utils::EntityExt; +use hyperion_utils::{structures::DamageCause, EntityExt}; use tracing::warn; use valence_protocol::{packets::play, VarInt}; use valence_server::GameMode; @@ -13,84 +12,6 @@ use valence_server::GameMode; pub mod command; pub mod module; -// /!\ Minecraft version dependent -pub enum DamageType { - InFire, - LightningBolt, - OnFire, - Lava, - HotFloor, - InWall, - Cramming, - Drown, - Starve, - Cactus, - Fall, - FlyIntoWall, - FellOutOfWorld, - Generic, - Magic, - Wither, - DragonBreath, - DryOut, - SweetBerryBush, - Freeze, - Stalagmite, - FallingBlock, - FallingAnvil, - FallingStalactite, - Sting, - MobAttack, - MobAttackNoAggro, - PlayerAttack, - Arrow, - Trident, - MobProjectile, - Fireworks, - UnattributedFireball, - Fireball, - WitherSkull, - Thrown, - IndirectMagic, - Thorns, - Explosion, - PlayerExplosion, - SonicBoom, - BadRespawnPoint, - OutsideBorder, - GenericKill, -} - -pub struct DamageCause { - pub damage_type: DamageType, - pub position: Option, - pub source_entity: i32, - pub direct_source: i32, -} - -impl DamageCause { - #[must_use] - pub const fn new(damage_type: DamageType) -> Self { - Self { - damage_type, - position: Option::None, - source_entity: 0, - direct_source: 0, - } - } - - pub const fn with_position(&mut self, position: DVec3) -> &mut Self { - self.position = Option::Some(position); - self - } - - pub const fn with_entities(&mut self, source: i32, direct_source: i32) -> &mut Self { - self.source_entity = source; - self.direct_source = direct_source; - self - } -} - #[must_use] pub const fn is_invincible(gamemode: &GameMode) -> bool { matches!(gamemode, GameMode::Creative | GameMode::Spectator) @@ -104,11 +25,22 @@ pub fn damage_player( system: EntityView<'_>, ) -> bool { if entity.has::() { - entity.get::<(&mut Health, &mut LastDamaged, &ConnectionId)>( - |(health, last_damaged, connection)| { - if !health.is_dead() && compose.global().tick - last_damaged.tick >= 20 { - health.damage(amount); + entity.get::<(&mut Health, &mut LastDamaged, &Position)>(|(health, last_damaged, pos)| { + if !health.is_dead() { + let mut applied_damages = 0.; + + if compose.global().tick - last_damaged.tick >= 10 { + applied_damages = amount; last_damaged.tick = compose.global().tick; + } else if compose.global().tick - last_damaged.tick < 10 + && last_damaged.amount < amount + { + applied_damages = amount - last_damaged.amount; + } + + if applied_damages > 0. { + last_damaged.amount = amount; + health.damage(applied_damages); let pkt_damage_event = play::EntityDamageS2c { entity_id: VarInt(entity.minecraft_id()), @@ -118,14 +50,18 @@ pub fn damage_player( source_pos: damage_cause.position, }; - compose - .unicast(&pkt_damage_event, *connection, system) - .unwrap(); // Should brodcast locally? + if compose + .broadcast_local(&pkt_damage_event, pos.to_chunk(), system) + .send() + .is_err() + { + warn!("Failed to brodacst EntityDamageS2c locally!"); + } return true; } - false - }, - ) + } + false + }) } else { warn!("Trying to call a Player only function on an non player entity"); false diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 71b20970..2f9dcb99 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -8,7 +8,7 @@ use flecs_ecs::{ use geometry::aabb::Aabb; use hyperion::{ glam::Vec3, - net::{agnostic, Compose}, + net::{agnostic, Compose, ConnectionId}, simulation::{ aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, metadata::entity::EntityFlags, BurningState, EntitySize, Gamemode, MovementTracking, Player, Position, @@ -16,12 +16,15 @@ use hyperion::{ storage::EventQueue, BlockKind, }; +use hyperion_utils::structures::{DamageCause, DamageType}; +use tracing::warn; +use valence_protocol::{packets::play, Sound}; use valence_server::{ block::{PropName, PropValue}, - ident, + text::IntoText, }; -use crate::{damage_player, is_invincible, DamageCause, DamageType}; +use crate::{damage_player, is_invincible}; #[derive(Component)] pub struct NaturalDamageModule {} @@ -61,9 +64,9 @@ impl Module for NaturalDamageModule { let sound = agnostic::sound( if event.fall_distance > 7. { - ident!("minecraft:entity.player.big_fall") + Sound::EntityPlayerBigFall.to_ident() } else { - ident!("minecraft:entity.player.small_fall") + Sound::EntityPlayerSmallFall.to_ident() }, **position, ) @@ -95,6 +98,22 @@ impl Module for NaturalDamageModule { let (min, max) = block_bounds(**position, *size); let min = min.with_y(min.y-1); let bounding_box = aabb(**position, *size); + let mut in_fire_source = false; + // water, powder snow, bubble column + TODO rain + let mut in_extinguisher = false; + + if burning.fire_ticks_left > 0 { + if burning.immune { + burning.fire_ticks_left = (burning.fire_ticks_left - 4).max(0); + }else { + if !burning.in_lava && burning.fire_ticks_left % 20 == 0 { + damage_player(&entity, 1., DamageCause::new(DamageType::OnFire), compose, system); + } + burning.fire_ticks_left -= 1; + } + } + + burning.in_lava = false; blocks.get_blocks(min, max, |pos, block| { let pos = Vec3::new(pos.x as f32, pos.y as f32, pos.z as f32); @@ -114,7 +133,7 @@ impl Module for NaturalDamageModule { damage_player(&entity, 1., DamageCause::new(DamageType::Cactus), compose, system); } BlockKind::MagmaBlock => { - if position.y > pos.y && !burning.immune{ + if position.y > pos.y && !burning.immune { damage_player(&entity, 1., DamageCause::new(DamageType::HotFloor), compose, system); } } @@ -129,46 +148,82 @@ impl Module for NaturalDamageModule { if Aabb::overlap(&aabb, &bounding_box).is_some() { match kind { BlockKind::Fire => { + in_fire_source = true; if !burning.immune { damage_player(&entity, 1., DamageCause::new(DamageType::InFire), compose, system); - burning.burn_for_seconds(8); + burning.fire_ticks_left += 1; + if burning.fire_ticks_left == 0 { + burning.burn_for_seconds(8); + } } } BlockKind::SoulFire => { + in_fire_source = true; if !burning.immune { damage_player(&entity, 2., DamageCause::new(DamageType::InFire), compose, system); - burning.burn_for_seconds(8); + burning.fire_ticks_left += 1; + if burning.fire_ticks_left == 0 { + burning.burn_for_seconds(8); + } } } BlockKind::Lava => { + in_fire_source = true; + burning.in_lava = true; if !burning.immune { - damage_player(&entity, 4., DamageCause::new(DamageType::InFire), compose, system); + if damage_player(&entity, 4., DamageCause::new(DamageType::InFire), compose, system) { + let sound = agnostic::sound( + Sound::EntityGenericBurn.to_ident(), + **position, + ) + .volume(0.4) + .pitch(2.) // 2.0F + this.random.nextFloat() * 0.4F + .seed(fastrand::i64(..)) + .build(); + + if compose.broadcast_local(&sound, position.to_chunk(), system).send().is_err() { + warn!("Failed to send burn sound to players"); + } + } burning.burn_for_seconds(15); - // this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F); } } BlockKind::SweetBerryBush => { - let grown = block.get(PropName::Age) - .is_some_and(|x| x != PropValue::_0); + let grown = block.get(PropName::Age).is_some_and(|x| x != PropValue::_0); let delta_x = (f64::from(position.x) - f64::from(movement.last_tick_position.x)).abs(); let delta_y = (f64::from(position.y) - f64::from(movement.last_tick_position.y)).abs(); - if grown && delta_x >= 0.003_000_000_026_077_032 && delta_y >= 0.003_000_000_026_077_032 && movement.last_tick_position != **position { + if grown && (delta_x >= 0.003_000_000_026_077_032 || delta_y >= 0.003_000_000_026_077_032) { damage_player(&entity, 1., DamageCause::new(DamageType::SweetBerryBush), compose, system); } } + BlockKind::Water | BlockKind::BubbleColumn | BlockKind::PowderSnow => { + in_extinguisher = true; + } _ => {} } } ControlFlow::Continue(()) }); - // TODO vanilla doesnt put the player on fire if they dont stay in fire longer than a certain threshold - if burning.fire_ticks_left > 0 { - if !burning.immune && burning.fire_ticks_left % 20 == 0 { - damage_player(&entity, 1., DamageCause::new(DamageType::OnFire), compose, system); + if burning.fire_ticks_left > 0 && in_extinguisher { + if !in_fire_source { + let sound = agnostic::sound( + Sound::EntityGenericExtinguishFire.to_ident(), + **position, + ) + .volume(0.7) + .pitch(1.) + .seed(fastrand::i64(..)) + .build(); + + if compose.broadcast_local(&sound, position.to_chunk(), system).send().is_err() { + warn!("Failed to send extinguish sound to players"); + } } - burning.fire_ticks_left -= 1; + burning.fire_ticks_left = -20; // -1 for every entities except players + }else if !in_fire_source && burning.fire_ticks_left <= 0 { + burning.fire_ticks_left = -20; // -1 for every entities except players } if burning.fire_ticks_left > 0 { @@ -190,5 +245,8 @@ pub const fn is_harmful_block(kind: BlockKind) -> bool { | BlockKind::SweetBerryBush | BlockKind::SoulFire | BlockKind::Fire + | BlockKind::Water + | BlockKind::BubbleColumn + | BlockKind::PowderSnow ) } From 8a2c583099ac286ef00ef075eddaced40c4ed271 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Fri, 7 Mar 2025 02:23:49 +0100 Subject: [PATCH 10/13] DamageType fixes --- crates/hyperion-utils/src/structures.rs | 68 +++++++++---------- crates/vanilla-behaviors/src/lib.rs | 6 +- .../src/module/natural_damage.rs | 11 ++- .../vanilla-behaviors/src/module/respawn.rs | 2 +- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/crates/hyperion-utils/src/structures.rs b/crates/hyperion-utils/src/structures.rs index e64aab54..a3490732 100644 --- a/crates/hyperion-utils/src/structures.rs +++ b/crates/hyperion-utils/src/structures.rs @@ -2,50 +2,50 @@ use valence_protocol::math::DVec3; // /!\ Minecraft version dependent pub enum DamageType { - InFire, - LightningBolt, - OnFire, - Lava, - HotFloor, - InWall, + Arrow, + BadRespawnPoint, + Cactus, Cramming, + DragonBreath, + DryOut, Drown, - Starve, - Cactus, + Explosion, Fall, + FallingAnvil, + FallingBlock, + FallingStalactite, + Fireball, + Fireworks, FlyIntoWall, - FellOutOfWorld, + Freeze, Generic, + GenericKill, + HotFloor, + InFire, + InWall, + IndirectMagic, + Lava, + LightningBolt, Magic, - Wither, - DragonBreath, - DryOut, - SweetBerryBush, - Freeze, - Stalagmite, - FallingBlock, - FallingAnvil, - FallingStalactite, - Sting, MobAttack, MobAttackNoAggro, + MobProjectile, + OnFire, + OutOfWorld, + OutsideBorder, PlayerAttack, - Arrow, + PlayerExplosion, + SonicBoom, + Stalagmite, + Sting, + Starve, + SweetBerryBush, + Thorns, + Thrown, Trident, - MobProjectile, - Fireworks, UnattributedFireball, - Fireball, + Wither, WitherSkull, - Thrown, - IndirectMagic, - Thorns, - Explosion, - PlayerExplosion, - SonicBoom, - BadRespawnPoint, - OutsideBorder, - GenericKill, } pub struct DamageCause { @@ -61,8 +61,8 @@ impl DamageCause { Self { damage_type, position: Option::None, - source_entity: 0, - direct_source: 0, + source_entity: -1, + direct_source: -1, } } diff --git a/crates/vanilla-behaviors/src/lib.rs b/crates/vanilla-behaviors/src/lib.rs index 0a2dd371..941d1b47 100644 --- a/crates/vanilla-behaviors/src/lib.rs +++ b/crates/vanilla-behaviors/src/lib.rs @@ -44,9 +44,9 @@ pub fn damage_player( let pkt_damage_event = play::EntityDamageS2c { entity_id: VarInt(entity.minecraft_id()), - source_cause_id: VarInt(damage_cause.source_entity), - source_direct_id: VarInt(damage_cause.direct_source), source_type_id: VarInt(damage_cause.damage_type as i32), + source_cause_id: VarInt(damage_cause.source_entity + 1), + source_direct_id: VarInt(damage_cause.direct_source + 1), source_pos: damage_cause.position, }; @@ -55,7 +55,7 @@ pub fn damage_player( .send() .is_err() { - warn!("Failed to brodacst EntityDamageS2c locally!"); + warn!("Failed to brodcast EntityDamageS2c locally!"); } return true; } diff --git a/crates/vanilla-behaviors/src/module/natural_damage.rs b/crates/vanilla-behaviors/src/module/natural_damage.rs index 2f9dcb99..e24c85fd 100644 --- a/crates/vanilla-behaviors/src/module/natural_damage.rs +++ b/crates/vanilla-behaviors/src/module/natural_damage.rs @@ -8,7 +8,7 @@ use flecs_ecs::{ use geometry::aabb::Aabb; use hyperion::{ glam::Vec3, - net::{agnostic, Compose, ConnectionId}, + net::{agnostic, Compose}, simulation::{ aabb, block_bounds, blocks::Blocks, event::HitGroundEvent, metadata::entity::EntityFlags, BurningState, EntitySize, Gamemode, MovementTracking, Player, Position, @@ -18,11 +18,8 @@ use hyperion::{ }; use hyperion_utils::structures::{DamageCause, DamageType}; use tracing::warn; -use valence_protocol::{packets::play, Sound}; -use valence_server::{ - block::{PropName, PropValue}, - text::IntoText, -}; +use valence_protocol::Sound; +use valence_server::block::{PropName, PropValue}; use crate::{damage_player, is_invincible}; @@ -171,7 +168,7 @@ impl Module for NaturalDamageModule { in_fire_source = true; burning.in_lava = true; if !burning.immune { - if damage_player(&entity, 4., DamageCause::new(DamageType::InFire), compose, system) { + if damage_player(&entity, 4., DamageCause::new(DamageType::Lava), compose, system) { let sound = agnostic::sound( Sound::EntityGenericBurn.to_ident(), **position, diff --git a/crates/vanilla-behaviors/src/module/respawn.rs b/crates/vanilla-behaviors/src/module/respawn.rs index 2a6a75db..63b50ccb 100644 --- a/crates/vanilla-behaviors/src/module/respawn.rs +++ b/crates/vanilla-behaviors/src/module/respawn.rs @@ -66,7 +66,7 @@ impl Module for RespawnModule { burning, )| { health.heal(20.); - burning.fire_ticks_left = 0; + burning.fire_ticks_left = -20; *pose = Pose::Standing; client.modified::(); // this is so observers detect the change From c6106cb4ddb130838d5308e835407be6a9ae261a Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Sat, 8 Mar 2025 04:30:09 +0100 Subject: [PATCH 11/13] less crashes but still some crashes --- crates/hyperion/src/egress/player_join/mod.rs | 13 +----------- crates/hyperion/src/simulation/handlers.rs | 21 ++++++++++++------- crates/hyperion/src/simulation/mod.rs | 19 ++++++++++++++++- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/hyperion/src/egress/player_join/mod.rs b/crates/hyperion/src/egress/player_join/mod.rs index 1fbc9a18..3d39e791 100644 --- a/crates/hyperion/src/egress/player_join/mod.rs +++ b/crates/hyperion/src/egress/player_join/mod.rs @@ -2,7 +2,6 @@ use std::{borrow::Cow, collections::BTreeSet, ops::Index}; use anyhow::Context; use flecs_ecs::prelude::*; -use glam::DVec3; use hyperion_crafting::{Action, CraftingRegistry, RecipeBookState}; use hyperion_utils::EntityExt; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -19,7 +18,7 @@ use valence_registry::{BiomeRegistry, RegistryCodec}; use valence_server::entity::EntityKind; use valence_text::IntoText; -use crate::simulation::{Gamemode, MovementTracking, PacketState, Pitch}; +use crate::simulation::{Gamemode, PacketState, Pitch}; mod list; pub use list::*; @@ -78,16 +77,6 @@ pub fn player_join_world( let id = entity.minecraft_id(); - entity.set(MovementTracking { - received_movement_packets: 0, - last_tick_flying: false, - last_tick_position: **position, - fall_start_y: position.y, - server_velocity: DVec3::ZERO, - sprinting: false, - was_on_ground: false, - }); - let registry_codec = registry_codec_raw(); let codec = RegistryCodec::default(); diff --git a/crates/hyperion/src/simulation/handlers.rs b/crates/hyperion/src/simulation/handlers.rs index 665d94be..fa25c4d6 100644 --- a/crates/hyperion/src/simulation/handlers.rs +++ b/crates/hyperion/src/simulation/handlers.rs @@ -89,7 +89,8 @@ fn change_position_or_correct_client( .set(PendingTeleportation::new(pose.position)); } query - .view + .id + .entity_view(query.world) .get::<(&mut MovementTracking, &Yaw)>(|(tracking, yaw)| { tracking.received_movement_packets += 1; let y_delta = proposed.y - pose.y; @@ -345,14 +346,20 @@ fn client_command( query.size.height = 1.8; } ClientCommand::StartSprinting => { - query.view.get::<&mut MovementTracking>(|tracking| { - tracking.sprinting = true; - }); + query + .id + .entity_view(query.world) + .get::<&mut MovementTracking>(|tracking| { + tracking.sprinting = true; + }); } ClientCommand::StopSprinting => { - query.view.get::<&mut MovementTracking>(|tracking| { - tracking.sprinting = false; - }); + query + .id + .entity_view(query.world) + .get::<&mut MovementTracking>(|tracking| { + tracking.sprinting = false; + }); } ClientCommand::StartJumpWithHorse | ClientCommand::StopJumpWithHorse diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index b02a8a53..4df154ba 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -613,7 +613,7 @@ impl Default for FlyingSpeed { } } -#[derive(Component, Default, Debug, Copy, Clone)] +#[derive(Component, Debug, Copy, Clone)] pub struct MovementTracking { pub fall_start_y: f32, pub last_tick_flying: bool, @@ -624,6 +624,20 @@ pub struct MovementTracking { pub was_on_ground: bool, } +impl Default for MovementTracking { + fn default() -> Self { + Self { + fall_start_y: -300., + last_tick_flying: false, + last_tick_position: Vec3::ZERO, + received_movement_packets: 0, + server_velocity: DVec3::ZERO, + sprinting: false, + was_on_ground: true, + } + } +} + #[derive(Component, Default, Debug, Copy, Clone)] #[meta] pub struct Flight { @@ -750,6 +764,9 @@ impl Module for SimModule { world .component::() .add_trait::<(flecs::With, hyperion_inventory::CursorItem)>(); + world + .component::() + .add_trait::<(flecs::With, MovementTracking)>(); observer!( world, From a4fe01e2091a04ea50d8ceabfe16f33da2f23d8e Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Sat, 8 Mar 2025 19:52:02 +0100 Subject: [PATCH 12/13] fix server pinging --- crates/hyperion/src/simulation/mod.rs | 65 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index 4df154ba..15af7751 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -895,47 +895,60 @@ impl Module for SimModule { observer!( world, flecs::OnSet, &FlyingSpeed, - &Compose($), &ConnectionId, &Flight + &Compose($), &ConnectionId, &Flight, &PacketState ) - .each_iter(|it, _, (flying_speed, compose, connection, flight)| { - let system = it.system(); + .each_iter( + |it, _, (flying_speed, compose, connection, flight, state)| { + if !matches!(state, PacketState::Play) { + return; + } + let system = it.system(); - let pkt = PlayerAbilitiesS2c { - flags: PlayerAbilitiesFlags::default() - .with_allow_flying(flight.allow) - .with_flying(flight.is_flying), - flying_speed: flying_speed.speed, - fov_modifier: 0.0, - }; + let pkt = PlayerAbilitiesS2c { + flags: PlayerAbilitiesFlags::default() + .with_allow_flying(flight.allow) + .with_flying(flight.is_flying), + flying_speed: flying_speed.speed, + fov_modifier: 0.0, + }; - compose.unicast(&pkt, *connection, system).unwrap(); - }); + compose.unicast(&pkt, *connection, system).unwrap(); + }, + ); observer!( world, flecs::OnSet, &Flight, - &Compose($), &ConnectionId, &FlyingSpeed + &Compose($), &ConnectionId, &FlyingSpeed, &PacketState ) - .each_iter(|it, _, (flight, compose, connection, flying_speed)| { - let system = it.system(); + .each_iter( + |it, _, (flight, compose, connection, flying_speed, state)| { + if !matches!(state, PacketState::Play) { + return; + } + let system = it.system(); - let pkt = play::PlayerAbilitiesS2c { - flags: PlayerAbilitiesFlags::default() - .with_allow_flying(flight.allow) - .with_flying(flight.is_flying), - flying_speed: flying_speed.speed, - fov_modifier: 0., - }; + let pkt = play::PlayerAbilitiesS2c { + flags: PlayerAbilitiesFlags::default() + .with_allow_flying(flight.allow) + .with_flying(flight.is_flying), + flying_speed: flying_speed.speed, + fov_modifier: 0., + }; - compose.unicast(&pkt, *connection, system).unwrap(); - }); + compose.unicast(&pkt, *connection, system).unwrap(); + }, + ); observer!( world, flecs::OnSet, &Gamemode, - &Compose($), &ConnectionId + &Compose($), &ConnectionId, &PacketState ) - .each_iter(|it, _, (gamemode, compose, connection)| { + .each_iter(|it, _, (gamemode, compose, connection, state)| { + if !matches!(state, PacketState::Play) { + return; + } let system = it.system(); let pkt = play::GameStateChangeS2c { From 39e4258da6fac61ef246303083b079a48893c437 Mon Sep 17 00:00:00 2001 From: GroobleDierne Date: Sun, 9 Mar 2025 02:34:21 +0100 Subject: [PATCH 13/13] fix some crashes --- crates/hyperion/src/egress/player_join/mod.rs | 2 +- crates/hyperion/src/simulation/handlers.rs | 3 +-- events/tag/src/module/spawn.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/hyperion/src/egress/player_join/mod.rs b/crates/hyperion/src/egress/player_join/mod.rs index 3d39e791..6f1d30a4 100644 --- a/crates/hyperion/src/egress/player_join/mod.rs +++ b/crates/hyperion/src/egress/player_join/mod.rs @@ -565,7 +565,7 @@ impl Module for PlayerJoinModule { let entity = world.entity_from_id(entity); - entity.get::<( + entity.try_get::<( &Uuid, &Name, &Position, diff --git a/crates/hyperion/src/simulation/handlers.rs b/crates/hyperion/src/simulation/handlers.rs index fa25c4d6..d4a8ca14 100644 --- a/crates/hyperion/src/simulation/handlers.rs +++ b/crates/hyperion/src/simulation/handlers.rs @@ -151,8 +151,7 @@ pub fn is_grounded(position: &Vec3, blocks: &Blocks) -> bool { // Check if the block at the calculated position is not air !blocks .get_block(IVec3::new(block_x, block_y, block_z)) - .unwrap() - .is_air() + .is_some_and(|block| block.is_air()) } fn has_block_collision(position: &Vec3, size: EntitySize, blocks: &Blocks) -> bool { diff --git a/events/tag/src/module/spawn.rs b/events/tag/src/module/spawn.rs index 334fa036..82a2d329 100644 --- a/events/tag/src/module/spawn.rs +++ b/events/tag/src/module/spawn.rs @@ -19,7 +19,7 @@ use rustc_hash::FxHashMap; pub struct SpawnModule; const RADIUS: i32 = 0; -const SPAWN_MIN_Y: i16 = 3; +const SPAWN_MIN_Y: i16 = 4; const SPAWN_MAX_Y: i16 = 100; fn position_in_radius() -> IVec2 {