Crate unification (#88)

* move client/server/config into shared

* move platforms into shared

* move head drops into shared

* move tb_entities to shared

* reduce server to just a call into shared

* get solo play working

* fix server opening window

* fix fmt

* extracted a few more modules from client

* near completely migrated client

* fixed duplicate CharacterInputEnabled definition

* simplify a few things related to builds

* more simplifications

* fix warnings/check

* ci update

* address comments

* try fixing macos steam build

* address comments

* address comments

* CI tweaks with default client feature

---------

Co-authored-by: PROMETHIA-27 <electriccobras@gmail.com>
This commit is contained in:
extrawurst
2025-12-18 18:31:22 +01:00
committed by GitHub
parent c80129dac1
commit 7cfae285ed
100 changed files with 1099 additions and 1791 deletions

View File

@@ -0,0 +1,29 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
pub struct AutoRotation(pub Quat);
pub fn plugin(app: &mut App) {
app.register_type::<AutoRotation>();
#[cfg(feature = "client")]
app.add_systems(Update, update_auto_rotation);
}
#[cfg(feature = "client")]
fn update_auto_rotation(
query: Query<(&AutoRotation, &Children)>,
mut meshes: Query<&mut Transform>,
) {
for (auto_rotation, children) in query.iter() {
for &child in children {
let Ok(mut transform) = meshes.get_mut(child) else {
continue;
};
transform.rotate_local(auto_rotation.0);
}
}
}

View File

@@ -0,0 +1,75 @@
use crate::camera::MainCamera;
use bevy::prelude::*;
use bevy_sprite3d::Sprite3dPlugin;
use serde::{Deserialize, Serialize};
#[derive(Component, Reflect, Default, PartialEq, Eq, Serialize, Deserialize)]
#[reflect(Component)]
pub enum Billboard {
#[default]
All,
XZ,
}
pub fn plugin(app: &mut App) {
if !app.is_plugin_added::<Sprite3dPlugin>() {
app.add_plugins(Sprite3dPlugin);
}
app.register_type::<Billboard>();
app.add_systems(Update, (face_camera, face_camera_no_parent));
}
fn face_camera(
cam_query: Query<&GlobalTransform, With<MainCamera>>,
mut query: Query<
(&mut Transform, &ChildOf, &InheritedVisibility, &Billboard),
Without<MainCamera>,
>,
parent_transform: Query<&GlobalTransform>,
) {
let Ok(cam_transform) = cam_query.single() else {
return;
};
for (mut transform, parent, visible, billboard) in query.iter_mut() {
if !matches!(*visible, InheritedVisibility::VISIBLE) {
continue;
}
let Ok(parent_global) = parent_transform.get(parent.parent()) else {
continue;
};
let target = cam_transform.reparented_to(parent_global);
let target = match *billboard {
Billboard::All => target.translation,
Billboard::XZ => Vec3::new(
target.translation.x,
transform.translation.y,
target.translation.z,
),
};
transform.look_at(target, Vec3::Y);
}
}
fn face_camera_no_parent(
cam_query: Query<&GlobalTransform, With<MainCamera>>,
mut query: Query<(&mut Transform, &Billboard), (Without<MainCamera>, Without<ChildOf>)>,
) {
let Ok(cam_transform) = cam_query.single() else {
return;
};
for (mut transform, billboard) in query.iter_mut() {
let target = cam_transform.translation();
let target = match *billboard {
Billboard::All => cam_transform.translation(),
Billboard::XZ => Vec3::new(target.x, transform.translation.y, target.z),
};
transform.look_at(target, Vec3::Y);
}
}

View File

@@ -0,0 +1,37 @@
use crate::{global_observer, hitpoints::Hit, physics_layers::GameLayer};
use avian3d::prelude::*;
use bevy::prelude::*;
#[derive(Event, Debug)]
pub struct Explosion {
pub position: Vec3,
pub radius: f32,
pub damage: u32,
}
pub fn plugin(app: &mut App) {
global_observer!(app, on_explosion);
}
fn on_explosion(explosion: On<Explosion>, mut commands: Commands, spatial_query: SpatialQuery) {
let explosion = explosion.event();
let intersections = {
spatial_query.shape_intersections(
&Collider::sphere(explosion.radius),
explosion.position,
Quat::default(),
&SpatialQueryFilter::default().with_mask(LayerMask(
GameLayer::Npc.to_bits() | GameLayer::Player.to_bits(),
)),
)
};
for entity in intersections.iter() {
if let Ok(mut e) = commands.get_entity(*entity) {
e.trigger(|entity| Hit {
entity,
damage: explosion.damage,
});
}
}
}

View File

@@ -0,0 +1,16 @@
pub mod auto_rotate;
pub mod billboards;
pub mod explosions;
pub mod observers;
pub mod one_shot_force;
pub mod run_conditions;
pub mod sprite_3d_animation;
pub mod squish_animation;
pub mod trail;
use bevy::prelude::*;
pub(crate) use observers::global_observer;
pub fn plugin(app: &mut App) {
app.add_plugins(one_shot_force::plugin);
}

View File

@@ -0,0 +1,56 @@
#[macro_export]
macro_rules! global_observer {
($app:expr, $($system:tt)*) => {{
$app.world_mut()
.add_observer($($system)*)
.insert(global_observer!(@name $($system)*))
}};
(@name $system:ident ::< $($param:ident),+ $(,)? >) => {{
let mut name = String::new();
name.push_str(stringify!($system));
name.push_str("::<");
$(
name.push_str(std::any::type_name::<$param>());
)+
name.push_str(">");
Name::new(name)
}};
(@name $system:expr) => {
Name::new(stringify!($system))
};
}
pub use global_observer;
#[macro_export]
macro_rules! server_observer {
($app:expr, $($system:tt)*) => {{
$app.add_systems(OnEnter(::bevy_replicon::prelude::ClientState::Disconnected), |mut commands: Commands| {
commands
.add_observer($($system)*)
.insert((
global_observer!(@name $($system)*),
DespawnOnExit(::bevy_replicon::prelude::ClientState::Disconnected),
));
})
}};
(@name $system:ident ::< $($param:ident),+ $(,)? >) => {{
let mut name = String::new();
name.push_str(stringify!($system));
name.push_str("::<");
$(
name.push_str(std::any::type_name::<$param>());
)+
name.push_str(">");
Name::new(name)
}};
(@name $system:expr) => {
Name::new(stringify!($system))
};
}
pub use server_observer;

View File

@@ -0,0 +1,19 @@
use avian3d::prelude::{Forces, RigidBodyForces};
use bevy::prelude::*;
pub fn plugin(app: &mut App) {
app.add_systems(FixedUpdate, apply_one_shot_forces);
}
#[derive(Component)]
pub struct OneShotImpulse(pub Vec3);
pub fn apply_one_shot_forces(
mut commands: Commands,
mut query: Query<(Entity, &OneShotImpulse, Forces)>,
) {
for (entity, force, mut forces) in query.iter_mut() {
forces.apply_linear_impulse(force.0);
commands.entity(entity).remove::<OneShotImpulse>();
}
}

View File

@@ -0,0 +1,5 @@
use bevy::ecs::{resource::Resource, system::Res};
pub fn resource_absent<R: Resource>(res: Option<Res<R>>) -> bool {
res.is_none()
}

View File

@@ -0,0 +1,39 @@
use bevy::prelude::*;
use bevy_sprite3d::Sprite3d;
#[derive(Component, Reflect, Deref, DerefMut)]
#[reflect(Component)]
pub struct AnimationTimer(Timer);
impl AnimationTimer {
pub fn new(t: Timer) -> Self {
Self(t)
}
}
pub fn plugin(app: &mut App) {
app.add_systems(Update, animate_sprite);
}
fn animate_sprite(
mut commands: Commands,
time: Res<Time>,
mut query: Query<(Entity, &mut AnimationTimer, &Sprite3d, &mut Sprite)>,
) {
for (e, mut timer, sprite_3d, mut sprite) in query.iter_mut() {
let length = sprite_3d.texture_atlas_keys.len();
let atlas = sprite.texture_atlas.as_mut().unwrap();
if length > 0 {
timer.tick(time.delta());
}
if timer.just_finished() {
if atlas.index + 1 < length {
atlas.index = (atlas.index + 1) % length;
} else {
commands.entity(e).despawn();
}
}
}
}

View File

@@ -0,0 +1,21 @@
use bevy::prelude::*;
use ops::sin;
use serde::{Deserialize, Serialize};
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
pub struct SquishAnimation(pub f32);
pub fn plugin(app: &mut App) {
app.add_systems(Update, update);
}
fn update(mut query: Query<(&mut Transform, &SquishAnimation)>, time: Res<Time>) {
for (mut transform, keymesh) in query.iter_mut() {
transform.scale = Vec3::new(
keymesh.0,
keymesh.0 + (sin(time.elapsed_secs() * 6.) * 0.2),
keymesh.0,
);
}
}

View File

@@ -0,0 +1,131 @@
use crate::GameState;
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Component, Reflect, Deserialize, Serialize)]
#[reflect(Component)]
pub struct SpawnTrail {
pub points: usize,
pub col_start: LinearRgba,
pub col_end: LinearRgba,
pub width: f32,
pub init_pos: bool,
}
impl SpawnTrail {
pub fn new(points: usize, col_start: LinearRgba, col_end: LinearRgba, width: f32) -> Self {
Self {
points,
col_start,
col_end,
width,
init_pos: false,
}
}
pub fn init_with_pos(mut self) -> Self {
self.init_pos = true;
self
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct Trail {
points: Vec<Vec3>,
col_start: LinearRgba,
col_end: LinearRgba,
}
impl Trail {
pub fn new(trail: SpawnTrail) -> Self {
Self {
points: Vec::with_capacity(trail.points),
col_start: trail.col_start,
col_end: trail.col_end,
}
}
pub fn with_pos(self, pos: Option<Vec3>) -> Self {
let mut trail = self;
if let Some(pos) = pos {
trail.add(pos);
}
trail
}
pub fn add(&mut self, pos: Vec3) {
if self.points.len() >= self.points.capacity() {
self.points.pop();
}
self.points.insert(0, pos);
}
}
pub fn plugin(app: &mut App) {
app.add_systems(
FixedUpdate,
update_trail.run_if(in_state(GameState::Playing)),
);
#[cfg(feature = "client")]
app.add_systems(Update, attach_trail.run_if(in_state(GameState::Playing)));
}
#[cfg(feature = "client")]
fn attach_trail(
mut commands: Commands,
query: Query<(Entity, &Transform, &SpawnTrail), Added<SpawnTrail>>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
for (entity, transform, trail) in query.iter() {
let width = trail.width;
let init_pos = trail.init_pos.then_some(transform.translation);
let id = commands
.spawn((
Trail::new(*trail).with_pos(init_pos),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig { width, ..default() },
..default()
},
))
.id();
commands
.entity(entity)
.queue_silenced(move |mut world: EntityWorldMut| {
world.add_child(id);
});
}
}
fn update_trail(
mut query: Query<(Entity, &mut Trail, &Gizmo, &GlobalTransform)>,
global_transform: Query<&GlobalTransform>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) -> Result {
for (e, mut trail, gizmo, pos) in query.iter_mut() {
trail.add(pos.translation());
let parent_transform = global_transform.get(e)?;
let Some(gizmo) = gizmo_assets.get_mut(gizmo.handle.id()) else {
continue;
};
gizmo.clear();
let lerp_denom = trail.points.len() as f32;
gizmo.linestrip_gradient(trail.points.iter().enumerate().map(|(i, pos)| {
(
GlobalTransform::from_translation(*pos)
.reparented_to(parent_transform)
.translation,
trail.col_start.mix(&trail.col_end, i as f32 / lerp_denom),
)
}));
}
Ok(())
}