diff --git a/cosmos_client/src/netty/gameplay/receiver.rs b/cosmos_client/src/netty/gameplay/receiver.rs index 57b440549..1ac1abbf5 100644 --- a/cosmos_client/src/netty/gameplay/receiver.rs +++ b/cosmos_client/src/netty/gameplay/receiver.rs @@ -816,6 +816,7 @@ pub(crate) fn client_sync_players( structure_entity, block: ev.block, new_health: ev.new_health, + causer: ev.causer.and_then(|x| network_mapping.client_from_server(&x)), }) })); } diff --git a/cosmos_client/src/projectiles/lasers.rs b/cosmos_client/src/projectiles/lasers.rs index 617be0ff1..1eb5be39b 100644 --- a/cosmos_client/src/projectiles/lasers.rs +++ b/cosmos_client/src/projectiles/lasers.rs @@ -1,6 +1,6 @@ //! Handles the creation of lasers -use bevy::prelude::*; +use bevy::{prelude::*, utils::HashMap}; use bevy_rapier3d::{plugin::RapierContextEntityLink, prelude::RapierContextSimulation}; use bevy_renet2::renet2::*; use cosmos_core::{ @@ -10,7 +10,7 @@ use cosmos_core::{ system_sets::NetworkingSystemsSet, NettyChannelServer, }, physics::location::CosmosBundleSet, - projectiles::laser::Laser, + projectiles::{causer::Causer, laser::Laser}, state::GameState, }; @@ -22,7 +22,11 @@ use crate::structure::{ #[derive(Resource)] struct LaserMesh(Handle); +#[derive(Resource, Default)] +struct LaserMaterials(HashMap>); + fn create_laser_mesh(mut meshes: ResMut>, mut commands: Commands) { + commands.init_resource::(); commands.insert_resource(LaserMesh(meshes.add(Mesh::from(Cuboid::new(0.1, 0.1, 1.0))))); } @@ -37,6 +41,7 @@ fn lasers_netty( mut ev_writer_missile_launcher_fired: EventWriter, mut q_shield_render: Query<&mut ShieldRender>, q_default_world: Query>, + mut laser_materials: ResMut, ) { while let Some(message) = client.receive_message(NettyChannelServer::StructureSystems) { let msg: ServerStructureSystemMessages = cosmos_encoder::deserialize(&message).unwrap(); @@ -49,6 +54,7 @@ fn lasers_netty( firer_velocity, strength, mut no_hit, + causer, } => { if let Some(server_entity) = no_hit { if let Some(client_entity) = network_mapping.client_from_server(&server_entity) { @@ -56,6 +62,28 @@ fn lasers_netty( } } + let causer = causer.map(|c| network_mapping.client_from_server(&c.0)).and_then(|e| e.map(Causer)); + + fn color_hash(color: Srgba) -> u32 { + let (r, g, b, a) = ( + (color.red * 255.0) as u8, + (color.green * 255.0) as u8, + (color.blue * 255.0) as u8, + (color.alpha * 255.0) as u8, + ); + + u32::from_be_bytes([r, g, b, a]) + } + let color = color.unwrap_or(Color::WHITE); + + let material = laser_materials.0.entry(color_hash(color.into())).or_insert_with(|| { + materials.add(StandardMaterial { + base_color: color, + unlit: true, + ..Default::default() + }) + }); + Laser::spawn_custom_pbr( location, laser_velocity, @@ -64,17 +92,13 @@ fn lasers_netty( no_hit, CosmosPbrBundle { mesh: Mesh3d(laser_mesh.0.clone_weak()), - material: MeshMaterial3d(materials.add(StandardMaterial { - base_color: color.unwrap_or(Color::WHITE), - // emissive: color, - unlit: true, - ..Default::default() - })), + material: MeshMaterial3d(material.clone_weak()), ..Default::default() }, &time, RapierContextEntityLink(q_default_world.single()), &mut commands, + causer, ); } ServerStructureSystemMessages::LaserCannonSystemFired { ship_entity } => { diff --git a/cosmos_core/src/netty/server_laser_cannon_system_messages.rs b/cosmos_core/src/netty/server_laser_cannon_system_messages.rs index 85903406f..6a065e8ea 100644 --- a/cosmos_core/src/netty/server_laser_cannon_system_messages.rs +++ b/cosmos_core/src/netty/server_laser_cannon_system_messages.rs @@ -3,7 +3,7 @@ use bevy::prelude::{Color, Component, Entity, Vec3}; use serde::{Deserialize, Serialize}; -use crate::physics::location::Location; +use crate::{physics::location::Location, projectiles::causer::Causer}; #[derive(Debug, Serialize, Deserialize, Component)] /// All the laser cannon system messages @@ -22,6 +22,8 @@ pub enum ServerStructureSystemMessages { strength: f32, /// Which entity this laser shouldn't hit (None if it should hit all) no_hit: Option, + /// Who fired this laser + causer: Option, }, /// Sent whenever a laser cannon system is fired LaserCannonSystemFired { diff --git a/cosmos_core/src/netty/server_reliable_messages.rs b/cosmos_core/src/netty/server_reliable_messages.rs index 5678a31a2..4f2d0948d 100644 --- a/cosmos_core/src/netty/server_reliable_messages.rs +++ b/cosmos_core/src/netty/server_reliable_messages.rs @@ -50,6 +50,8 @@ pub struct BlockHealthUpdate { pub block: StructureBlock, /// The block's new health pub new_health: f32, + /// The entity that caused this change + pub causer: Option, } #[derive(Debug, Serialize, Deserialize, Component)] diff --git a/cosmos_core/src/physics/stop_near_unloaded_chunks.rs b/cosmos_core/src/physics/stop_near_unloaded_chunks.rs index 89ff7be12..19dbffb39 100644 --- a/cosmos_core/src/physics/stop_near_unloaded_chunks.rs +++ b/cosmos_core/src/physics/stop_near_unloaded_chunks.rs @@ -1,11 +1,12 @@ //! Freezes entities that are near unloaded chunks so they don't fly into unloaded areas. -use bevy::prelude::{App, Commands, Entity, GlobalTransform, PostUpdate, Query, With, Without}; +use bevy::prelude::{App, Commands, Entity, GlobalTransform, Parent, PostUpdate, Query, With, Without}; use bevy_rapier3d::prelude::Collider; use crate::{ ecs::NeedsDespawned, physics::location::SECTOR_DIMENSIONS, + projectiles::laser::Laser, structure::{ asteroid::Asteroid, coordinates::{UnboundChunkCoordinate, UnboundCoordinateType}, @@ -23,7 +24,16 @@ const FREEZE_RADIUS: UnboundCoordinateType = 1; const REASON: &str = "cosmos:stop_near_unloaded_chunks"; fn stop_near_unloaded_chunks( - mut query: Query<(Entity, &Location, Option<&mut DisableRigidBody>), (Without, Without, Without)>, + mut query: Query< + (Entity, &Location, Option<&mut DisableRigidBody>), + ( + Without, + Without, + Without, + Without, + Without, + ), + >, structures: Query<(Entity, &Structure, &Location, &GlobalTransform)>, has_collider: Query<(), With>, mut commands: Commands, diff --git a/cosmos_core/src/projectiles/causer.rs b/cosmos_core/src/projectiles/causer.rs new file mode 100644 index 000000000..024791d6a --- /dev/null +++ b/cosmos_core/src/projectiles/causer.rs @@ -0,0 +1,51 @@ +//! Used to identify the causer of this projectile. +//! +//! Typically used to determine damage source + +use bevy::{ + prelude::{App, Component, Entity}, + reflect::Reflect, +}; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "client")] +use crate::netty::sync::mapping::Mappable; +#[cfg(feature = "client")] +use crate::netty::sync::mapping::MappingError; + +#[derive(Component, Debug, Reflect, Clone, Copy, Serialize, Deserialize)] +/// Identifies who fired this projectile. +pub struct Causer(pub Entity); + +#[cfg(feature = "client")] +impl Mappable for Causer { + fn map( + self, + network_mapping: &crate::netty::sync::mapping::NetworkMapping, + ) -> Result> + where + Self: Sized, + { + network_mapping + .client_from_server(&self.0) + .map(|x| Ok(Self(x))) + .unwrap_or(Err(MappingError::MissingRecord(self))) + } + + fn map_to_server( + self, + network_mapping: &crate::netty::sync::mapping::NetworkMapping, + ) -> Result> + where + Self: Sized, + { + network_mapping + .server_from_client(&self.0) + .map(|x| Ok(Self(x))) + .unwrap_or(Err(MappingError::MissingRecord(self))) + } +} + +pub(super) fn register(app: &mut App) { + app.register_type::(); +} diff --git a/cosmos_core/src/projectiles/laser.rs b/cosmos_core/src/projectiles/laser.rs index 2185d2134..02dee9842 100644 --- a/cosmos_core/src/projectiles/laser.rs +++ b/cosmos_core/src/projectiles/laser.rs @@ -27,6 +27,8 @@ use crate::{ structure::chunk::ChunkEntity, }; +use super::causer::Causer; + /// How long a laser will stay alive for before despawning pub const LASER_LIVE_TIME: Duration = Duration::from_secs(5); @@ -44,6 +46,7 @@ pub struct LaserCollideEvent { entity_hit: Entity, local_position_hit: Vec3, laser_strength: f32, + causer: Option, } impl LaserCollideEvent { @@ -63,6 +66,11 @@ impl LaserCollideEvent { pub fn local_position_hit(&self) -> Vec3 { self.local_position_hit } + + /// Returns the entity that caused this laser to be fired + pub fn causer(&self) -> Option { + self.causer + } } #[derive(Component)] @@ -106,6 +114,7 @@ impl Laser { time: &Time, context_entity_link: RapierContextEntityLink, commands: &mut Commands, + causer: Option, ) -> Entity { pbr.rotation = Transform::from_xyz(0.0, 0.0, 0.0) .looking_at(laser_velocity, Vec3::Y) @@ -137,6 +146,10 @@ impl Laser { NoSendEntity, )); + if let Some(causer) = causer { + ent_cmds.insert(causer); + } + if let Some(ent) = no_collide_entity { ent_cmds.insert(NoCollide(ent)); } @@ -157,6 +170,7 @@ impl Laser { time: &Time, context_entity_link: RapierContextEntityLink, commands: &mut Commands, + causer: Option, ) -> Entity { Self::spawn_custom_pbr( position, @@ -168,6 +182,7 @@ impl Laser { time, context_entity_link, commands, + causer, ) } } @@ -182,6 +197,7 @@ fn send_laser_hit_events( &mut Laser, &Velocity, Option<&WorldWithin>, + Option<&Causer>, ), With, >, @@ -193,7 +209,7 @@ fn send_laser_hit_events( worlds: Query<(&Location, &RapierContextEntityLink, Entity), With>, q_rapier_context: WriteRapierContext, ) { - for (world, location, laser_entity, no_collide_entity, mut laser, velocity, world_within) in query.iter_mut() { + for (world, location, laser_entity, no_collide_entity, mut laser, velocity, world_within, causer) in query.iter_mut() { if laser.active { let last_pos = laser.last_position; let delta_position = last_pos.relative_coords_to(location); @@ -254,6 +270,7 @@ fn send_laser_hit_events( entity_hit: entity, local_position_hit: lph, laser_strength: laser.strength, + causer: causer.copied(), }); } } else if let Ok(transform) = transform_query.get(entity) { @@ -265,6 +282,7 @@ fn send_laser_hit_events( entity_hit: entity, local_position_hit: lph, laser_strength: laser.strength, + causer: causer.copied(), }); } diff --git a/cosmos_core/src/projectiles/mod.rs b/cosmos_core/src/projectiles/mod.rs index 6a4d79d8f..b5a344f1e 100644 --- a/cosmos_core/src/projectiles/mod.rs +++ b/cosmos_core/src/projectiles/mod.rs @@ -2,10 +2,12 @@ use bevy::prelude::App; +pub mod causer; pub mod laser; pub mod missile; pub(super) fn register(app: &mut App) { + causer::register(app); laser::register(app); missile::register(app); } diff --git a/cosmos_core/src/structure/base_structure.rs b/cosmos_core/src/structure/base_structure.rs index e59435d1d..d9c66e818 100644 --- a/cosmos_core/src/structure/base_structure.rs +++ b/cosmos_core/src/structure/base_structure.rs @@ -321,7 +321,7 @@ impl BaseStructure { } /// Gets the block at these block coordinates - pub fn block_at<'a>(&'a self, coords: BlockCoordinate, blocks: &'a Registry) -> &'a Block { + pub fn block_at<'a>(&self, coords: BlockCoordinate, blocks: &'a Registry) -> &'a Block { let id = self.block_id_at(coords); blocks.from_numeric_id(id) } @@ -486,6 +486,7 @@ impl BaseStructure { blocks: &Registry, amount: f32, event_writers: Option<(&mut EventWriter, &mut EventWriter)>, + causer: Option, ) -> Option { if let Some(chunk) = self.mut_chunk_at_block_coordinates(coords) { let health_left = chunk.block_take_damage(ChunkBlockCoordinate::for_block_coordinate(coords), amount, blocks); @@ -498,6 +499,7 @@ impl BaseStructure { structure_entity, block, new_health: health_left, + causer, }); if health_left <= 0.0 { destroyed_event_writer.send(BlockDestroyedEvent { structure_entity, block }); diff --git a/cosmos_core/src/structure/block_health/events.rs b/cosmos_core/src/structure/block_health/events.rs index 6c890ba53..12e7db963 100644 --- a/cosmos_core/src/structure/block_health/events.rs +++ b/cosmos_core/src/structure/block_health/events.rs @@ -13,15 +13,20 @@ pub struct BlockDestroyedEvent { pub block: StructureBlock, } -/// This event is sent when a block is destroyed +/// This event is sent when a block takes damage #[derive(Debug, Event)] pub struct BlockTakeDamageEvent { - /// The structure that had its block destroyed + /// The structure that had its block take damage pub structure_entity: Entity, /// The block that took damage pub block: StructureBlock, /// The block's new health pub new_health: f32, + /// The entity that caused this damage if there is one + /// + /// This is NOT the direct causer (such as a laser or missile), but rather the entity that caused the damage + /// (such as the ship that fired the laser). + pub causer: Option, } pub(super) fn register(app: &mut App) { diff --git a/cosmos_core/src/structure/chunk/mod.rs b/cosmos_core/src/structure/chunk/mod.rs index 268d21c0c..354fc6370 100644 --- a/cosmos_core/src/structure/chunk/mod.rs +++ b/cosmos_core/src/structure/chunk/mod.rs @@ -119,8 +119,9 @@ impl BlockStorer for Chunk { fn set_block_at(&mut self, coords: ChunkBlockCoordinate, b: &Block, block_rotation: BlockRotation) { let new_id = b.id(); let old_id = self.block_at(coords); + let old_rot = self.block_rotation(coords); - if new_id == old_id { + if new_id == old_id && block_rotation == old_rot { return; } diff --git a/cosmos_core/src/structure/mod.rs b/cosmos_core/src/structure/mod.rs index d5b008176..c28fa9e05 100644 --- a/cosmos_core/src/structure/mod.rs +++ b/cosmos_core/src/structure/mod.rs @@ -290,7 +290,7 @@ impl Structure { } /// Gets the block at these block coordinates - pub fn block_at<'a>(&'a self, coords: BlockCoordinate, blocks: &'a Registry) -> &'a Block { + pub fn block_at<'a>(&self, coords: BlockCoordinate, blocks: &'a Registry) -> &'a Block { match self { Self::Full(fs) => fs.block_at(coords, blocks), Self::Dynamic(ds) => ds.block_at(coords, blocks), @@ -485,10 +485,11 @@ impl Structure { blocks: &Registry, amount: f32, event_writers: Option<(&mut EventWriter, &mut EventWriter)>, + causer: Option, ) -> Option { match self { - Self::Full(fs) => fs.block_take_damage(coords, blocks, amount, event_writers), - Self::Dynamic(ds) => ds.block_take_damage(coords, blocks, amount, event_writers), + Self::Full(fs) => fs.block_take_damage(coords, blocks, amount, event_writers, causer), + Self::Dynamic(ds) => ds.block_take_damage(coords, blocks, amount, event_writers, causer), } } diff --git a/cosmos_core/src/structure/structure_iterator.rs b/cosmos_core/src/structure/structure_iterator.rs index 25f5dd692..c0014a730 100644 --- a/cosmos_core/src/structure/structure_iterator.rs +++ b/cosmos_core/src/structure/structure_iterator.rs @@ -418,3 +418,64 @@ impl<'a> Iterator for ChunkIterator<'a> { } } } + +#[cfg(test)] +mod test { + use crate::{ + block::{block_builder::BlockBuilder, Block}, + prelude::FullStructure, + registry::{identifiable::Identifiable, Registry}, + utils::random::random_range, + }; + + use super::*; + #[test] + fn test_iterator() { + const SIZE: ChunkCoordinate = ChunkCoordinate::new(2, 2, 2); + + let mut s = Structure::Full(FullStructure::new(SIZE)); + + let mut blocks = Registry::::new("cosmos:blocks"); + blocks.register(BlockBuilder::new("air", 0.0, 0.0, 0.0).create()); + blocks.register(BlockBuilder::new("asdf", 1.0, 1.0, 1.0).create()); + + for z in 0..s.block_dimensions().z { + for y in 0..s.block_dimensions().y { + for x in 0..s.block_dimensions().x { + let id = random_range(0.0, blocks.iter().len() as f32 - 1.0).round() as u16; + + s.set_block_at( + BlockCoordinate::new(x, y, z), + blocks.from_numeric_id(id), + Default::default(), + &blocks, + None, + ); + } + } + } + + let mut duplicate = Structure::Full(FullStructure::new(SIZE)); + + for c in s.all_blocks_iter(false) { + duplicate.set_block_at(c, s.block_at(c, &blocks), Default::default(), &blocks, None); + } + + for z in 0..s.block_dimensions().z { + for y in 0..s.block_dimensions().y { + for x in 0..s.block_dimensions().x { + let coords = BlockCoordinate::new(x, y, z); + let a = s.block_at(coords, &blocks); + let b = duplicate.block_at(coords, &blocks); + assert_eq!( + a, + b, + "Block @ {coords} failed - {} != {}", + a.unlocalized_name(), + b.unlocalized_name() + ); + } + } + } + } +} diff --git a/cosmos_server/default_blueprints/pirate/default_0.bp b/cosmos_server/default_blueprints/pirate/default_0.bp index d4f83f48b..4c763e9f4 100644 Binary files a/cosmos_server/default_blueprints/pirate/default_0.bp and b/cosmos_server/default_blueprints/pirate/default_0.bp differ diff --git a/cosmos_server/default_blueprints/pirate/default_1.bp b/cosmos_server/default_blueprints/pirate/default_1.bp index 06925979b..9e7388e30 100644 Binary files a/cosmos_server/default_blueprints/pirate/default_1.bp and b/cosmos_server/default_blueprints/pirate/default_1.bp differ diff --git a/cosmos_server/default_blueprints/pirate/default_2.bp b/cosmos_server/default_blueprints/pirate/default_2.bp index ea058bbbb..82146b553 100644 Binary files a/cosmos_server/default_blueprints/pirate/default_2.bp and b/cosmos_server/default_blueprints/pirate/default_2.bp differ diff --git a/cosmos_server/default_blueprints/pirate/default_3.bp b/cosmos_server/default_blueprints/pirate/default_3.bp index 27d9431dd..b43b3eab5 100644 Binary files a/cosmos_server/default_blueprints/pirate/default_3.bp and b/cosmos_server/default_blueprints/pirate/default_3.bp differ diff --git a/cosmos_server/default_blueprints/shop/default.bp b/cosmos_server/default_blueprints/shop/default.bp index f02e2a485..2ecef795d 100644 Binary files a/cosmos_server/default_blueprints/shop/default.bp and b/cosmos_server/default_blueprints/shop/default.bp differ diff --git a/cosmos_server/src/netty/sync/sync_bodies.rs b/cosmos_server/src/netty/sync/sync_bodies.rs index 54f73037f..6b1b00331 100644 --- a/cosmos_server/src/netty/sync/sync_bodies.rs +++ b/cosmos_server/src/netty/sync/sync_bodies.rs @@ -146,7 +146,7 @@ fn notify_client_of_successful_entity_request( } fn notify_despawned_entities( - removed_components: Query, Without)>, + removed_components: Query, (Without, Without))>, q_identifier: Query<(Option<&StructureSystem>, Option<&ItemStackData>, Option<&BlockData>)>, mut server: ResMut, ) { diff --git a/cosmos_server/src/projectiles/explosion.rs b/cosmos_server/src/projectiles/explosion.rs index 01e3cfe77..3ea0a489a 100644 --- a/cosmos_server/src/projectiles/explosion.rs +++ b/cosmos_server/src/projectiles/explosion.rs @@ -25,7 +25,10 @@ use cosmos_core::{ player_world::{PlayerWorld, WorldWithin}, structure_physics::ChunkPhysicsPart, }, - projectiles::missile::{Explosion, ExplosionSystemSet}, + projectiles::{ + causer::Causer, + missile::{Explosion, ExplosionSystemSet}, + }, registry::Registry, structure::{ block_health::events::{BlockDestroyedEvent, BlockTakeDamageEvent}, @@ -58,7 +61,17 @@ pub struct ExplosionHitEvent { fn respond_to_explosion( mut commands: Commands, - q_explosions: Query<(Entity, &Location, &WorldWithin, &RapierContextEntityLink, &Explosion), Added>, + q_explosions: Query< + ( + Entity, + &Location, + &WorldWithin, + &RapierContextEntityLink, + &Explosion, + Option<&Causer>, + ), + Added, + >, q_player_world: Query<&Location, With>, q_excluded: Query<(), Or<(With, Without)>>, @@ -73,7 +86,7 @@ fn respond_to_explosion( q_shield: Query<&Shield>, ) { - for (ent, &explosion_loc, world_within, physics_world, &explosion) in q_explosions.iter() { + for (ent, &explosion_loc, world_within, physics_world, &explosion, causer) in q_explosions.iter() { commands.entity(ent).insert((NeedsDespawned, DontNotifyClientOfDespawn)); let Ok(player_world_loc) = q_player_world.get(world_within.0) else { @@ -175,6 +188,7 @@ fn respond_to_explosion( &blocks_registry, explosion_power * HEALTH_PER_EXPLOSION_POWER, Some((&mut evw_block_take_damage, &mut evw_block_destroyed)), + causer.map(|x| x.0), ); } } diff --git a/cosmos_server/src/projectiles/laser.rs b/cosmos_server/src/projectiles/laser.rs index fbf56bbc3..034c49fde 100644 --- a/cosmos_server/src/projectiles/laser.rs +++ b/cosmos_server/src/projectiles/laser.rs @@ -2,7 +2,10 @@ use bevy::prelude::*; use cosmos_core::{ block::Block, netty::system_sets::NetworkingSystemsSet, - projectiles::laser::{Laser, LaserCollideEvent, LaserSystemSet}, + projectiles::{ + causer::Causer, + laser::{Laser, LaserCollideEvent, LaserSystemSet}, + }, registry::Registry, state::GameState, structure::{ @@ -27,6 +30,7 @@ fn on_laser_hit_structure( block_take_damage_event_writer: &mut EventWriter, block_destroy_event_writer: &mut EventWriter, strength: f32, + causer: Option<&Causer>, ) { if let Ok(coords) = structure.relative_coords_to_local_coords_checked(local_position_hit.x, local_position_hit.y, local_position_hit.z) { @@ -35,6 +39,7 @@ fn on_laser_hit_structure( blocks, strength, Some((block_take_damage_event_writer, block_destroy_event_writer)), + causer.map(|x| x.0), ); } else { warn!("Bad laser hit spot that isn't actually on structure ;("); @@ -62,6 +67,7 @@ fn respond_laser_hit_event( &mut block_take_damage_event_writer, &mut block_destroy_event_writer, ev.laser_strength(), + ev.causer().as_ref(), ); } } diff --git a/cosmos_server/src/structure/block_health/mod.rs b/cosmos_server/src/structure/block_health/mod.rs index 4c84f4ba4..5534598ae 100644 --- a/cosmos_server/src/structure/block_health/mod.rs +++ b/cosmos_server/src/structure/block_health/mod.rs @@ -43,6 +43,7 @@ fn monitor_block_health_changed(mut server: ResMut, mut event_reade block: ev.block, new_health: ev.new_health, structure_entity: ev.structure_entity, + causer: ev.causer, }) .collect::>(); diff --git a/cosmos_server/src/structure/systems/laser_cannon_system.rs b/cosmos_server/src/structure/systems/laser_cannon_system.rs index 4631bfeea..c9b9fdbc5 100644 --- a/cosmos_server/src/structure/systems/laser_cannon_system.rs +++ b/cosmos_server/src/structure/systems/laser_cannon_system.rs @@ -13,7 +13,7 @@ use cosmos_core::{ NettyChannelServer, }, physics::location::Location, - projectiles::laser::Laser, + projectiles::{causer::Causer, laser::Laser}, registry::{identifiable::Identifiable, Registry}, state::GameState, structure::{ @@ -104,6 +104,8 @@ fn update_system( let strength = (5.0 * line.len as f32).powf(1.2); let no_hit = Some(system.structure_entity()); + let causer = Some(Causer(system.structure_entity())); + Laser::spawn( location, laser_velocity, @@ -113,6 +115,7 @@ fn update_system( &time, *physics_world, &mut commands, + causer, ); let color = line.color; @@ -126,6 +129,7 @@ fn update_system( firer_velocity: ship_velocity.linvel, strength, no_hit, + causer, }), ); } diff --git a/cosmos_server/src/universe/spawners/pirate.rs b/cosmos_server/src/universe/spawners/pirate.rs index 4b2f81880..0bb0ac8c4 100644 --- a/cosmos_server/src/universe/spawners/pirate.rs +++ b/cosmos_server/src/universe/spawners/pirate.rs @@ -13,45 +13,104 @@ use bevy::{ system::{Commands, Query, Res, Resource}, }, math::{Quat, Vec3}, + prelude::{Added, EventReader}, reflect::Reflect, state::condition::in_state, time::{common_conditions::on_timer, Time}, utils::hashbrown::HashMap, }; use cosmos_core::{ + block::block_events::BlockEventsSet, entities::player::Player, + netty::{sync::IdentifiableComponent, system_sets::NetworkingSystemsSet}, physics::location::{Location, Sector, SectorUnit, SECTOR_DIMENSIONS}, state::GameState, + structure::{block_health::events::BlockTakeDamageEvent, shared::MeltingDown, ship::pilot::Pilot}, utils::random::random_range, }; +use serde::{Deserialize, Serialize}; use crate::{ - persistence::loading::{LoadingBlueprintSystemSet, NeedsBlueprintLoaded}, + persistence::{ + loading::{LoadingBlueprintSystemSet, LoadingSystemSet, NeedsBlueprintLoaded}, + make_persistent::{make_persistent, PersistentComponent}, + }, settings::ServerSettings, }; +/// TODO: Load this from config +/// +/// Total playtime (sec) is divided by this to calculate its difficulty. +const TIME_DIFFICULTY_CONSTANT: f32 = 80_000.0; + +/// TODO: Load this from config +/// +/// How much one player's strength will increase the pirate's difficulty. +const PLAYER_STRENGTH_INCREASE_FACTOR: f32 = 1.0; + +/// TODO: Load this from config +/// +/// How much killing a pirate will increase the difficulty. +/// Aka, if you do 100% of the damage, your strength percentage will increase by this percent. +const DIFFICULTY_INCREASE: f32 = 5.0; + #[derive(Component)] /// A pirate needs spawned for this entity, please add the components it needs to function -pub struct PirateNeedsSpawned(Location); +pub struct PirateNeedsSpawned { + location: Location, + difficulty: u32, +} #[derive(Component)] /// A pirate-controlled ship pub struct Pirate; -const MAX_DIFFICULTY: u64 = 4; -const SECTORS_DIFFICULTY_INCREASE: u64 = 4; +/// The maximum difficulty of ship we can spawn. This is NOT the total difficulty. +const MAX_DIFFICULTY: u64 = 3; + +#[derive(Component, Reflect, Debug, Clone, Copy, Default, Serialize, Deserialize)] +/// Represents how the enemies perceive your strength as a percentage between 0.0 and 100.0. +/// +/// At 0.0%, the enemies will send their weakest fighters at you. At 100.0%, enemies will send +/// their most advanced fleets at you. +/// +/// Killing pirates increases your stength, and dying lowers it. +struct PlayerStrength(f32); + +impl IdentifiableComponent for PlayerStrength { + fn get_component_unlocalized_name() -> &'static str { + "cosmos:player_strength" + } +} + +impl PersistentComponent for PlayerStrength {} + +#[derive(Component, Reflect, Debug, Clone, Copy, Default, Serialize, Deserialize)] +/// Represents the total time a player has played on the server +/// +/// Used for difficulty calculations +struct TotalTimePlayed { + pub time_sec: u64, +} + +impl IdentifiableComponent for TotalTimePlayed { + fn get_component_unlocalized_name() -> &'static str { + "cosmos:total_time_played" + } +} + +impl PersistentComponent for TotalTimePlayed {} fn on_needs_pirate_spawned(mut commands: Commands, q_needs_pirate_spawned: Query<(Entity, &PirateNeedsSpawned)>) { for (ent, pns) in q_needs_pirate_spawned.iter() { - let difficulty = (pns.0.sector - Sector::new(25, 25, 25)).abs().max_element(); - let difficulty = (difficulty as u64 / SECTORS_DIFFICULTY_INCREASE).min(MAX_DIFFICULTY); + let difficulty = pns.difficulty; commands.entity(ent).remove::().insert(( Pirate, NeedsBlueprintLoaded { path: format!("default_blueprints/pirate/default_{difficulty}.bp"), rotation: Quat::IDENTITY, - spawn_at: pns.0, + spawn_at: pns.location, }, )); } @@ -69,7 +128,7 @@ fn add_spawn_times(q_players: Query, Without>, + q_players: Query<(Entity, &Location, &NextPirateSpawn, &TotalTimePlayed, &PlayerStrength), With>, time: Res