Files
HEDZReloaded/crates/hedz_reloaded/src/tb_entities.rs
extrawurst 7cfae285ed 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>
2025-12-18 12:31:22 -05:00

277 lines
7.8 KiB
Rust

use crate::{
GameState,
cash::Cash,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::{
SkipReplicateColliders, TbMapEntityId, TbMapIdCounter, messages::DespawnTbMapEntity,
},
utils::global_observer,
};
use avian3d::{
parry::{na::SVector, shape::SharedShape},
prelude::*,
};
use bevy::{
ecs::{lifecycle::HookContext, world::DeferredWorld},
math::*,
prelude::*,
};
use bevy_replicon::prelude::{ClientId, ConnectedClient, SendMode, ToClients};
use bevy_trenchbroom::prelude::*;
use serde::{Deserialize, Serialize};
#[point_class(base(Transform), model({ "path": "models/spawn.glb" }))]
#[derive(Default)]
#[component(on_add = Self::on_add)]
pub struct SpawnPoint {}
impl SpawnPoint {
fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let Some(assets) = world.get_resource::<GameAssets>() else {
return;
};
let mesh = assets.mesh_spawn.clone();
world.commands().entity(entity).insert((
Name::new("spawn"),
SceneRoot(mesh),
RigidBody::Static,
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
));
}
}
#[solid_class(
hooks(SpawnHooks::new().convex_collider())
)]
#[derive(Default)]
pub struct Worldspawn;
#[solid_class(base(Transform), hooks(SpawnHooks::new()))]
#[derive(Default)]
pub struct Water;
#[solid_class(base(Transform), hooks(SpawnHooks::new().convex_collider()))]
#[derive(Default)]
pub struct Crates;
#[solid_class(base(Transform), hooks(SpawnHooks::new().convex_collider()))]
#[derive(Default)]
pub struct NamedEntity {
pub name: String,
}
#[solid_class(base(Transform, Target), hooks(SpawnHooks::new().convex_collider()))]
#[derive(Default)]
#[require(RigidBody = RigidBody::Kinematic)]
pub struct Platform;
#[point_class(base(Transform))]
#[derive(Default)]
pub struct PlatformTarget {
pub targetname: String,
}
#[solid_class(base(Transform, Target), hooks(SpawnHooks::new().convex_collider()))]
#[derive(Default, Serialize, Deserialize, PartialEq)]
#[require(RigidBody = RigidBody::Kinematic)]
pub struct Movable {
pub name: String,
}
#[point_class(base(Transform))]
#[derive(Default)]
pub struct MoveTarget {
pub targetname: String,
}
#[point_class(base(Transform))]
#[derive(Default)]
pub struct CameraTarget {
pub targetname: String,
}
#[point_class(base(Transform, Target))]
#[derive(Default)]
pub struct CutsceneCamera {
pub name: String,
pub targetname: String,
}
#[point_class(base(Transform, Target))]
#[derive(Default)]
pub struct CutsceneCameraMovementEnd;
#[point_class(base(Transform), model({ "path": "models/alien_naked.glb" }))]
#[derive(Default)]
#[component(on_add = Self::on_add)]
pub struct EnemySpawn {
pub head: String,
pub key: String,
pub disable_ai: bool,
pub spawn_order: Option<u32>,
}
impl EnemySpawn {
fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
//TODO: figure out why this crashes if removed
let Some(_assets) = world.get_resource::<GameAssets>() else {
return;
};
let this = world.get_entity(entity).unwrap().get::<Self>().unwrap();
let this_transform = world
.get_entity(entity)
.unwrap()
.get::<Transform>()
.unwrap();
let mut this_transform = *this_transform;
this_transform.translation += Vec3::new(0., 1.5, 0.);
let position = Position::new(this_transform.translation);
let rotation = Rotation(this_transform.rotation);
let head = this.head.clone();
world.commands().entity(entity).insert((
this_transform,
position,
rotation,
Name::from(format!("enemy [{head}]")),
Visibility::default(),
RigidBody::Kinematic,
Collider::capsule(0.6, 2.),
CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL),
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
));
}
}
#[point_class(base(Transform), model({ "path": "models/cash.glb" }))]
#[derive(Default)]
#[component(on_add = Self::on_add)]
pub struct CashSpawn {}
impl CashSpawn {
fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let Some(assets) = world.get_resource::<GameAssets>() else {
return;
};
let mesh = assets.mesh_cash.clone();
world.commands().entity(entity).insert((
Name::new("cash"),
SceneRoot(mesh),
Cash,
Collider::cuboid(2., 3.0, 2.),
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::ALL),
RigidBody::Static,
CollisionEventsEnabled,
Sensor,
));
}
}
#[point_class(base(Transform), model({ "path": "models/head_drop.glb" }))]
#[derive(Default)]
pub struct SecretHead {
pub head_id: usize,
}
fn fix_target_tb_entities(
mut commands: Commands,
mut entities: Query<(Entity, &Transform, &Collider), With<Target>>,
) {
for (entity, tf, coll) in entities.iter_mut() {
if let Some(shape) = coll.shape().as_compound() {
let mut shapes: Vec<_> = shape.shapes().to_vec();
for shape in shapes.iter_mut() {
shape.0.translation.vector -= SVector::<f32, 3>::from(tf.translation.to_array());
}
commands
.entity(entity)
.insert(Collider::from(SharedShape::compound(shapes)));
}
}
}
pub fn plugin(app: &mut App) {
app.register_type::<DespawnedTbEntityCache>();
app.register_type::<SpawnPoint>();
app.override_class::<Worldspawn>();
app.register_type::<Water>();
app.register_type::<Crates>();
app.register_type::<NamedEntity>();
app.register_type::<Platform>();
app.register_type::<PlatformTarget>();
app.register_type::<Movable>();
app.register_type::<MoveTarget>();
app.register_type::<CameraTarget>();
app.register_type::<CutsceneCamera>();
app.register_type::<CutsceneCameraMovementEnd>();
app.register_type::<EnemySpawn>();
app.register_type::<CashSpawn>();
app.register_type::<SecretHead>();
app.init_resource::<DespawnedTbEntityCache>();
app.add_systems(OnExit(GameState::MapLoading), fix_target_tb_entities);
app.add_systems(
OnEnter(GameState::MapLoading),
|mut cache: ResMut<DespawnedTbEntityCache>| cache.0.clear(),
);
global_observer!(app, add_despawned_entities_to_cache);
global_observer!(app, send_new_client_despawned_cache);
global_observer!(app, tb_component_setup::<CashSpawn>);
global_observer!(app, tb_component_setup::<Movable>);
global_observer!(app, tb_component_setup::<Platform>);
global_observer!(app, tb_component_setup::<PlatformTarget>);
}
fn tb_component_setup<C: Component>(
trigger: On<Add, C>,
mut commands: Commands,
mut world: DeferredWorld,
) {
let id = world.resource_mut::<TbMapIdCounter>().alloc();
commands
.entity(trigger.event().entity)
.insert_if_new(id)
.insert(SkipReplicateColliders);
}
fn add_despawned_entities_to_cache(
trigger: On<Remove, TbMapEntityId>,
id: Query<&TbMapEntityId>,
mut cache: ResMut<DespawnedTbEntityCache>,
) {
cache.0.push(id.get(trigger.event().entity).unwrap().id);
}
#[derive(Default, Resource, Reflect)]
#[reflect(Resource)]
pub struct DespawnedTbEntityCache(pub Vec<u64>);
fn send_new_client_despawned_cache(
on: On<Add, ConnectedClient>,
cache: Res<DespawnedTbEntityCache>,
mut send: MessageWriter<ToClients<DespawnTbMapEntity>>,
) {
for &id in cache.0.iter() {
send.write(ToClients {
mode: SendMode::Direct(ClientId::Client(on.entity)),
message: DespawnTbMapEntity(id),
});
}
}