* 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>
277 lines
7.8 KiB
Rust
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),
|
|
});
|
|
}
|
|
}
|