Compare commits
32 Commits
decals
...
036f77d510
| Author | SHA1 | Date | |
|---|---|---|---|
| 036f77d510 | |||
| b85ce27b2a | |||
| 97929504cc | |||
| 6a82d294de | |||
| c2194d1c0b | |||
| d6b9d5c968 | |||
| 16aaf2a961 | |||
|
|
c69a528625 | ||
| 334eacfd1c | |||
| 07c4c43b6c | |||
| b5ec1229b8 | |||
| 3ce61bfe5b | |||
| a3d5eee474 | |||
| fc300dae01 | |||
| 1ff7fefe38 | |||
| 5bf718678a | |||
| 63c831f92f | |||
| 812dcbac7c | |||
| 69f9d73d80 | |||
| 61918b632e | |||
| cd804f50fa | |||
| 366f09d51f | |||
| de0d4a7b9c | |||
| 25cd3356b6 | |||
| 7274416661 | |||
| fb46879f93 | |||
| c7505a1395 | |||
| 4b8cc8bb71 | |||
| f6eb652fd2 | |||
| 34ac340399 | |||
| aee21e22e6 | |||
| 552786f27a |
444
Cargo.lock
generated
444
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -11,7 +11,7 @@ opt-level = 3
|
|||||||
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
|
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
avian3d = { version = "0.2", default-features = false, features = [
|
avian3d = { version = "0.3", default-features = false, features = [
|
||||||
"3d",
|
"3d",
|
||||||
"f32",
|
"f32",
|
||||||
"parry-f32",
|
"parry-f32",
|
||||||
@@ -21,18 +21,15 @@ avian3d = { version = "0.2", default-features = false, features = [
|
|||||||
"parallel",
|
"parallel",
|
||||||
] }
|
] }
|
||||||
bevy = { version = "0.16.0", features = ["track_location"] }
|
bevy = { version = "0.16.0", features = ["track_location"] }
|
||||||
bevy_trenchbroom = { version = "0.7", features = [
|
bevy_trenchbroom = { version = "0.8.0", features = ["avian"] }
|
||||||
"auto_register",
|
|
||||||
"avian",
|
|
||||||
"client",
|
|
||||||
] }
|
|
||||||
nil = "0.14.0"
|
nil = "0.14.0"
|
||||||
bevy_asset_loader = "0.23.0-rc.3"
|
bevy_asset_loader = "0.23.0-rc.3"
|
||||||
bevy_sprite3d = "5.0.0"
|
bevy_sprite3d = "5.0.0"
|
||||||
rand = "=0.8.5"
|
rand = "=0.8.5"
|
||||||
bevy-inspector-egui = { version = "0.31", optional = true }
|
bevy-inspector-egui = { version = "0.31", optional = true }
|
||||||
bevy-steamworks = "0.13.0"
|
bevy-steamworks = "0.13.0"
|
||||||
bevy_ballistic = "0.3.0"
|
steamworks = "0.11"
|
||||||
|
bevy_ballistic = "0.4.0"
|
||||||
bevy-ui-gradients = "0.4.0"
|
bevy-ui-gradients = "0.4.0"
|
||||||
bevy_debug_log = "0.6.0"
|
bevy_debug_log = "0.6.0"
|
||||||
bevy_common_assets = { version = "0.13.0", features = ["ron"] }
|
bevy_common_assets = { version = "0.13.0", features = ["ron"] }
|
||||||
@@ -47,7 +44,4 @@ too_many_arguments = "allow"
|
|||||||
type_complexity = "allow"
|
type_complexity = "allow"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
bevy-steamworks = { git = "https://github.com/HouraiTeahouse/bevy_steamworks.git", rev = "0bc62fc" }
|
bevy-steamworks = { git = "https://github.com/extrawurst/bevy_steamworks.git", branch = "derive-debug-event" }
|
||||||
avian3d = { git = "https://github.com/Jondolf/avian.git", rev = "9511076" }
|
|
||||||
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", branch = "bevy-0.16" }
|
|
||||||
bevy_trenchbroom = { git = "https://github.com/extrawurst/bevy_trenchbroom.git", branch = "bevy-0.16.0" }
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
([
|
([
|
||||||
/*00*/(key:"angry demonstrator", ability:Thrown, ammo: 10, range:90, damage:25, projectile:"molotov"),
|
/*00*/(key:"angry demonstrator", ability:Thrown, aps:2, ammo:10, range:90, damage:25, projectile:"molotov"),
|
||||||
/*01*/(key:"carnival knife thrower", range:60, ammo: 5 ),
|
/*01*/(key:"carnival knife thrower", range:60, ammo:5),
|
||||||
/*02*/(key:"chicago gangster", ability:Gun, ammo: 25, range:60),
|
/*02*/(key:"chicago gangster", ability:Gun, ammo:25, range:60),
|
||||||
/*03*/(key:"commando", ability:Gun, ammo: 26, range:60, damage:12),
|
/*03*/(key:"commando", ability:Gun, aps:7.4, ammo:26, range:60, damage:12),
|
||||||
/*04*/(key:"field medic"),
|
/*04*/(key:"field medic", ability:Medic, aps:20),
|
||||||
/*05*/(key:"geisha"),
|
/*05*/(key:"geisha"),
|
||||||
/*06*/(key:"goblin", ability:Arrow, ammo: 5, range:90, damage:50),
|
/*06*/(key:"goblin", ability:Arrow, aps:2, ammo:5, range:90, damage:50),
|
||||||
/*07*/(key:"green grocer", range:60, ammo: 10, damage: 25),
|
/*07*/(key:"green grocer", range:60, ammo:10, damage:25),
|
||||||
/*08*/(key:"highland hammer thrower", ability:Thrown, ammo: 10, damage: 25, range:80, projectile:"hammer"),
|
/*08*/(key:"highland hammer thrower", ability:Thrown, aps:2, ammo:10, damage:25, range:80, projectile:"hammer"),
|
||||||
/*09*/(key:"legionnaire", ability:Gun, ammo: 25, range:60, damage: 13),
|
/*09*/(key:"legionnaire", ability:Gun, aps:1.5, ammo:25, range:60, damage:13),
|
||||||
/*10*/(key:"mig pilot", ability:Missile, ammo: 5, range:60, damage:100, controls:Plane),
|
/*10*/(key:"mig pilot", ability:Missile, ammo:5, range:60, damage:100, controls:Plane),
|
||||||
/*11*/(key:"nanny", ability:Thrown, range:60),
|
/*11*/(key:"nanny", ability:Thrown, range:60),
|
||||||
/*12*/(key:"panic attack"),
|
/*12*/(key:"panic attack"),
|
||||||
/*13*/(key:"salty sea dog"),
|
/*13*/(key:"salty sea dog"),
|
||||||
/*14*/(key:"snow plough operator"),
|
/*14*/(key:"snow plough operator"),
|
||||||
/*15*/(key:"soldier ant"),
|
/*15*/(key:"soldier ant"),
|
||||||
/*16*/(key:"super market shopper", ability:Thrown, ammo: 10, range:80, damage: 17, projectile:"handbag"),
|
/*16*/(key:"super market shopper", ability:Thrown, ammo:10, range:80, damage:17, projectile:"handbag"),
|
||||||
/*17*/(key:"troll", ability:Thrown, ammo: 10, range:80, damage: 17),
|
/*17*/(key:"troll", ability:Thrown, ammo:10, range:80, damage:17),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1320,7 +1320,7 @@
|
|||||||
{
|
{
|
||||||
"classname" "move_target"
|
"classname" "move_target"
|
||||||
"origin" "1152 0 -216"
|
"origin" "1152 0 -216"
|
||||||
"angles" "0 0 0"
|
"angles" "0 -90 0"
|
||||||
"targetname" "target_fence_01"
|
"targetname" "target_fence_01"
|
||||||
}
|
}
|
||||||
// entity 20
|
// entity 20
|
||||||
@@ -1352,7 +1352,7 @@
|
|||||||
{
|
{
|
||||||
"classname" "move_target"
|
"classname" "move_target"
|
||||||
"origin" "1152 512 -216"
|
"origin" "1152 512 -216"
|
||||||
"angles" "0 -180 0"
|
"angles" "0 90 0"
|
||||||
"targetname" "target_fence_02"
|
"targetname" "target_fence_02"
|
||||||
}
|
}
|
||||||
// entity 22
|
// entity 22
|
||||||
@@ -1408,6 +1408,7 @@
|
|||||||
"classname" "move_target"
|
"classname" "move_target"
|
||||||
"origin" "1480 6600 -232"
|
"origin" "1480 6600 -232"
|
||||||
"targetname" "target_fence_shaft"
|
"targetname" "target_fence_shaft"
|
||||||
|
"angles" "0 -90 0"
|
||||||
}
|
}
|
||||||
// entity 27
|
// entity 27
|
||||||
{
|
{
|
||||||
@@ -1530,3 +1531,69 @@
|
|||||||
( 832 7360 -1568 ) ( 832 7360 -1567 ) ( 832 7361 -1568 ) water [ 0 1 0 0 ] [ 0 0 -1 -32 ] 270 1 1
|
( 832 7360 -1568 ) ( 832 7360 -1567 ) ( 832 7361 -1568 ) water [ 0 1 0 0 ] [ 0 0 -1 -32 ] 270 1 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// entity 40
|
||||||
|
{
|
||||||
|
"classname" "platform"
|
||||||
|
"target" "test_target"
|
||||||
|
"angles" "0 0 0"
|
||||||
|
// brush 0
|
||||||
|
{
|
||||||
|
( 368 1120 16 ) ( 368 1121 16 ) ( 368 1120 17 ) origin [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 368 1120 16 ) ( 368 1120 17 ) ( 369 1120 16 ) origin [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 368 1120 16 ) ( 369 1120 16 ) ( 368 1121 16 ) origin [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
|
||||||
|
( 400 1152 32 ) ( 400 1153 32 ) ( 401 1152 32 ) origin [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
|
||||||
|
( 400 1152 32 ) ( 401 1152 32 ) ( 400 1152 33 ) origin [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 400 1152 32 ) ( 400 1152 33 ) ( 400 1153 32 ) origin [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
}
|
||||||
|
// brush 1
|
||||||
|
{
|
||||||
|
( 256 976 -16 ) ( 256 977 -16 ) ( 256 976 -15 ) blue-metal [ 0 -1 0 16 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 320 1008 -16 ) ( 320 1008 -15 ) ( 321 1008 -16 ) blue-metal [ 1 0 0 -32 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 320 976 -48 ) ( 321 976 -48 ) ( 320 977 -48 ) blue-metal [ -1 0 0 32 ] [ 0 -1 0 16 ] 0 1 1
|
||||||
|
( 512 1200 16 ) ( 512 1201 16 ) ( 513 1200 16 ) blue-metal [ 1 0 0 -32 ] [ 0 -1 0 16 ] 180 1 1
|
||||||
|
( 512 1264 16 ) ( 513 1264 16 ) ( 512 1264 17 ) blue-metal [ -1 0 0 32 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 512 1200 16 ) ( 512 1200 17 ) ( 512 1201 16 ) blue-metal [ 0 1 0 -16 ] [ 0 0 -1 -144 ] 180 1 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// entity 41
|
||||||
|
{
|
||||||
|
"classname" "platform_target"
|
||||||
|
"origin" "-40 1128 24"
|
||||||
|
"targetname" "test_target"
|
||||||
|
}
|
||||||
|
// entity 42
|
||||||
|
{
|
||||||
|
"classname" "platform"
|
||||||
|
"target" "test_target_2"
|
||||||
|
"angles" "0 0 0"
|
||||||
|
// brush 0
|
||||||
|
{
|
||||||
|
( 256 720 -16 ) ( 256 721 -16 ) ( 256 720 -15 ) blue-metal [ 0 -1 0 -240 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 320 752 -16 ) ( 320 752 -15 ) ( 321 752 -16 ) blue-metal [ 1 0 0 -32 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 320 720 -48 ) ( 321 720 -48 ) ( 320 721 -48 ) blue-metal [ -1 0 0 32 ] [ 0 -1 0 -240 ] 0 1 1
|
||||||
|
( 512 944 16 ) ( 512 945 16 ) ( 513 944 16 ) blue-metal [ 1 0 0 -32 ] [ 0 -1 0 -240 ] 180 1 1
|
||||||
|
( 512 1008 16 ) ( 513 1008 16 ) ( 512 1008 17 ) blue-metal [ -1 0 0 32 ] [ 0 0 -1 -144 ] 90 1 1
|
||||||
|
( 512 944 16 ) ( 512 944 17 ) ( 512 945 16 ) blue-metal [ 0 1 0 240 ] [ 0 0 -1 -144 ] 180 1 1
|
||||||
|
}
|
||||||
|
// brush 1
|
||||||
|
{
|
||||||
|
( 368 864 16 ) ( 368 865 16 ) ( 368 864 17 ) origin [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 368 864 16 ) ( 368 864 17 ) ( 369 864 16 ) origin [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 368 864 16 ) ( 369 864 16 ) ( 368 865 16 ) origin [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
|
||||||
|
( 400 896 32 ) ( 400 897 32 ) ( 401 896 32 ) origin [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
|
||||||
|
( 400 896 32 ) ( 401 896 32 ) ( 400 896 33 ) origin [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
( 400 896 32 ) ( 400 896 33 ) ( 400 897 32 ) origin [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// entity 43
|
||||||
|
{
|
||||||
|
"classname" "platform_target"
|
||||||
|
"origin" "104 552 568"
|
||||||
|
"targetname" "test_target_2"
|
||||||
|
}
|
||||||
|
// entity 44
|
||||||
|
{
|
||||||
|
"classname" "secret_head"
|
||||||
|
"origin" "2712 5240 792"
|
||||||
|
"head_id" "13"
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/models/characters/soldier ant.glb
Normal file
BIN
assets/models/characters/soldier ant.glb
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/models/medic_particle.glb
Normal file
BIN
assets/models/medic_particle.glb
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/sfx/abilities/fall-land.ogg
Normal file
BIN
assets/sfx/abilities/fall-land.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/abilities/handbag_hit.ogg
Normal file
BIN
assets/sfx/abilities/handbag_hit.ogg
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/sfx/abilities/step01.ogg
Normal file
BIN
assets/sfx/abilities/step01.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/abilities/step02.ogg
Normal file
BIN
assets/sfx/abilities/step02.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/ambient/alien_gate.ogg
Normal file
BIN
assets/sfx/ambient/alien_gate.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/ambient/button.ogg
Normal file
BIN
assets/sfx/ambient/button.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/ambient/downtown_elevator.ogg
Normal file
BIN
assets/sfx/ambient/downtown_elevator.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/ambient/downtown_underground_elevator.ogg
Normal file
BIN
assets/sfx/ambient/downtown_underground_elevator.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/effects/beam_out.ogg
Normal file
BIN
assets/sfx/effects/beam_out.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/effects/secret_collected.ogg
Normal file
BIN
assets/sfx/effects/secret_collected.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/effects/secret_discovered.ogg
Normal file
BIN
assets/sfx/effects/secret_discovered.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/ui/volume.ogg
Normal file
BIN
assets/sfx/ui/volume.ogg
Normal file
Binary file not shown.
@@ -5,9 +5,13 @@ use crate::{
|
|||||||
tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
|
tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::{pbr::NotShadowCaster, prelude::*};
|
use bevy::{
|
||||||
|
input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest},
|
||||||
|
pbr::NotShadowCaster,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
||||||
use std::f32::consts::PI;
|
use std::{f32::consts::PI, time::Duration};
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct GunProjectile {
|
struct GunProjectile {
|
||||||
@@ -81,11 +85,21 @@ fn on_trigger_gun(
|
|||||||
query_transform: Query<&Transform>,
|
query_transform: Query<&Transform>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
|
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
|
||||||
|
gamepads: Query<(Entity, &Gamepad)>,
|
||||||
|
mut rumble_requests: EventWriter<GamepadRumbleRequest>,
|
||||||
) {
|
) {
|
||||||
let state = trigger.0;
|
let state = trigger.0;
|
||||||
|
|
||||||
commands.trigger(PlaySound::Gun);
|
commands.trigger(PlaySound::Gun);
|
||||||
|
|
||||||
|
for (e, _) in gamepads.iter() {
|
||||||
|
rumble_requests.write(GamepadRumbleRequest::Add {
|
||||||
|
gamepad: e,
|
||||||
|
duration: Duration::from_secs_f32(0.5),
|
||||||
|
intensity: GamepadRumbleIntensity::MAX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let rotation = if let Some(t) = state
|
let rotation = if let Some(t) = state
|
||||||
.target
|
.target
|
||||||
.and_then(|target| query_transform.get(target).ok())
|
.and_then(|target| query_transform.get(target).ok())
|
||||||
@@ -161,9 +175,13 @@ fn shot_collision(
|
|||||||
|
|
||||||
let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };
|
let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };
|
||||||
|
|
||||||
let shot_pos = query_shot.get(shot_entity).unwrap().1.translation;
|
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
||||||
|
entity.try_despawn();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
commands.entity(shot_entity).despawn();
|
let shot_pos = query_shot.get(shot_entity).unwrap().1.translation;
|
||||||
|
|
||||||
let texture_atlas = TextureAtlas {
|
let texture_atlas = TextureAtlas {
|
||||||
layout: assets.layout.clone(),
|
layout: assets.layout.clone(),
|
||||||
|
|||||||
76
src/abilities/healing.rs
Normal file
76
src/abilities/healing.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
GameState, global_observer, heads::ActiveHeads, heads_database::HeadsDatabase,
|
||||||
|
hitpoints::Hitpoints, loading_assets::AudioAssets,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Healing(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Event, Debug)]
|
||||||
|
pub enum HealingStateChanged {
|
||||||
|
Started,
|
||||||
|
Stopped,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plugin(app: &mut App) {
|
||||||
|
app.add_systems(Update, update.run_if(in_state(GameState::Playing)));
|
||||||
|
|
||||||
|
global_observer!(app, on_heal_start_stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_heal_start_stop(
|
||||||
|
trigger: Trigger<HealingStateChanged>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
assets: Res<AudioAssets>,
|
||||||
|
query: Query<&Healing>,
|
||||||
|
) {
|
||||||
|
if matches!(trigger.event(), HealingStateChanged::Started) {
|
||||||
|
let e = cmds
|
||||||
|
.spawn((
|
||||||
|
Name::new("sfx-heal"),
|
||||||
|
AudioPlayer::new(assets.healing.clone()),
|
||||||
|
PlaybackSettings {
|
||||||
|
mode: bevy::audio::PlaybackMode::Loop,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
cmds.entity(trigger.target())
|
||||||
|
.add_child(e)
|
||||||
|
.insert(Healing(e));
|
||||||
|
} else {
|
||||||
|
if let Ok(healing) = query.single() {
|
||||||
|
cmds.entity(healing.0).despawn();
|
||||||
|
}
|
||||||
|
cmds.entity(trigger.target()).remove::<Healing>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Healing>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
heads_db: Res<HeadsDatabase>,
|
||||||
|
) {
|
||||||
|
for (mut heads, mut hitpoints) in query.iter_mut() {
|
||||||
|
let Some(current) = heads.current() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let head = heads_db.head_stats(current.head);
|
||||||
|
|
||||||
|
if current.last_use + (1. / head.aps) > time.elapsed_secs() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let medic_hp = hitpoints.get().0;
|
||||||
|
if medic_hp > 0 {
|
||||||
|
if let Some(health) = heads.medic_heal(2, time.elapsed_secs()) {
|
||||||
|
hitpoints.set_health(health);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,10 +137,11 @@ fn on_trigger_missile(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
Trail::new(
|
Trail::new(
|
||||||
t.translation,
|
12,
|
||||||
LinearRgba::rgb(1., 0.0, 0.),
|
LinearRgba::rgb(1., 0.0, 0.),
|
||||||
LinearRgba::rgb(0.9, 0.9, 0.)
|
LinearRgba::rgb(0.9, 0.9, 0.)
|
||||||
),
|
)
|
||||||
|
.with_pos(t.translation),
|
||||||
Gizmo {
|
Gizmo {
|
||||||
handle: gizmo_assets.add(GizmoAsset::default()),
|
handle: gizmo_assets.add(GizmoAsset::default()),
|
||||||
line_config: GizmoLineConfig {
|
line_config: GizmoLineConfig {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
mod arrow;
|
mod arrow;
|
||||||
mod gun;
|
mod gun;
|
||||||
|
mod healing;
|
||||||
mod missile;
|
mod missile;
|
||||||
mod thrown;
|
mod thrown;
|
||||||
|
|
||||||
|
pub use healing::Healing;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
GameState,
|
||||||
aim::AimTarget,
|
aim::AimTarget,
|
||||||
character::CharacterHierarchy,
|
character::CharacterHierarchy,
|
||||||
global_observer,
|
global_observer,
|
||||||
@@ -14,6 +18,7 @@ use crate::{
|
|||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use healing::HealingStateChanged;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Event, Reflect)]
|
#[derive(Event, Reflect)]
|
||||||
@@ -33,6 +38,7 @@ pub enum HeadAbility {
|
|||||||
Thrown,
|
Thrown,
|
||||||
Gun,
|
Gun,
|
||||||
Missile,
|
Missile,
|
||||||
|
Medic,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Clone, Copy)]
|
#[derive(Debug, Reflect, Clone, Copy)]
|
||||||
@@ -74,17 +80,44 @@ pub struct TriggerThrow(pub TriggerData);
|
|||||||
#[derive(Event, Reflect)]
|
#[derive(Event, Reflect)]
|
||||||
pub struct TriggerMissile(pub TriggerData);
|
pub struct TriggerMissile(pub TriggerData);
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct TriggerStateRes {
|
||||||
|
cooldown: f32,
|
||||||
|
active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriggerStateRes {
|
||||||
|
pub fn is_active(&self) -> bool {
|
||||||
|
self.active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
|
app.init_resource::<TriggerStateRes>();
|
||||||
|
|
||||||
app.add_plugins(gun::plugin);
|
app.add_plugins(gun::plugin);
|
||||||
app.add_plugins(thrown::plugin);
|
app.add_plugins(thrown::plugin);
|
||||||
app.add_plugins(arrow::plugin);
|
app.add_plugins(arrow::plugin);
|
||||||
app.add_plugins(missile::plugin);
|
app.add_plugins(missile::plugin);
|
||||||
|
app.add_plugins(healing::plugin);
|
||||||
|
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
(update, update_heal_ability).run_if(in_state(GameState::Playing)),
|
||||||
|
);
|
||||||
|
|
||||||
global_observer!(app, on_trigger_state);
|
global_observer!(app, on_trigger_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_trigger_state(
|
fn on_trigger_state(trigger: Trigger<TriggerState>, mut res: ResMut<TriggerStateRes>) {
|
||||||
trigger: Trigger<TriggerState>,
|
res.active = matches!(trigger.event(), TriggerState::Active);
|
||||||
|
if !res.active {
|
||||||
|
res.cooldown = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
mut res: ResMut<TriggerStateRes>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
player_rot: Query<&Transform, With<PlayerBodyMesh>>,
|
player_rot: Query<&Transform, With<PlayerBodyMesh>>,
|
||||||
player_query: Query<(Entity, &AimTarget), With<Player>>,
|
player_query: Query<(Entity, &AimTarget), With<Player>>,
|
||||||
@@ -93,7 +126,7 @@ fn on_trigger_state(
|
|||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
character: CharacterHierarchy,
|
character: CharacterHierarchy,
|
||||||
) {
|
) {
|
||||||
if matches!(trigger.event(), TriggerState::Active) {
|
if res.active && res.cooldown < time.elapsed_secs() {
|
||||||
let Some(state) = active_heads.current() else {
|
let Some(state) = active_heads.current() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -118,6 +151,16 @@ fn on_trigger_state(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let head = heads_db.head_stats(state.head);
|
||||||
|
|
||||||
|
if matches!(head.ability, HeadAbility::None | HeadAbility::Medic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
active_heads.use_ammo(time.elapsed_secs());
|
||||||
|
|
||||||
|
res.cooldown = time.elapsed_secs() + (1. / head.aps);
|
||||||
|
|
||||||
let trigger_state = TriggerData {
|
let trigger_state = TriggerData {
|
||||||
dir,
|
dir,
|
||||||
rot,
|
rot,
|
||||||
@@ -127,15 +170,39 @@ fn on_trigger_state(
|
|||||||
head: state.head,
|
head: state.head,
|
||||||
};
|
};
|
||||||
|
|
||||||
active_heads.use_ammo(time.elapsed_secs());
|
match head.ability {
|
||||||
|
|
||||||
let ability = heads_db.head_stats(state.head).ability;
|
|
||||||
match ability {
|
|
||||||
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
||||||
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
||||||
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
||||||
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
|
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
|
||||||
HeadAbility::None => (),
|
_ => panic!("Unhandled head ability"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_heal_ability(
|
||||||
|
res: Res<TriggerStateRes>,
|
||||||
|
mut commands: Commands,
|
||||||
|
active_heads: Single<(Entity, &ActiveHeads), With<Player>>,
|
||||||
|
heads_db: Res<HeadsDatabase>,
|
||||||
|
) {
|
||||||
|
if res.is_changed() {
|
||||||
|
let Some(state) = active_heads.1.current() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let player = active_heads.0;
|
||||||
|
|
||||||
|
let head = heads_db.head_stats(state.head);
|
||||||
|
|
||||||
|
if !matches!(head.ability, HeadAbility::Medic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.active {
|
||||||
|
commands.trigger_targets(HealingStateChanged::Started, player);
|
||||||
|
} else {
|
||||||
|
commands.trigger_targets(HealingStateChanged::Stopped, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ fn on_trigger_thrown(
|
|||||||
impact_animation: explosion_animation,
|
impact_animation: explosion_animation,
|
||||||
damage: head.damage,
|
damage: head.damage,
|
||||||
},
|
},
|
||||||
Collider::sphere(0.5),
|
Collider::sphere(0.4),
|
||||||
CollisionLayers::new(
|
CollisionLayers::new(
|
||||||
LayerMask(GameLayer::Projectile.to_bits()),
|
LayerMask(GameLayer::Projectile.to_bits()),
|
||||||
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
||||||
@@ -172,7 +172,11 @@ fn shot_collision(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.entity(shot_entity).despawn();
|
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
||||||
|
entity.try_despawn();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
commands.trigger(PlaySound::ThrowHit);
|
commands.trigger(PlaySound::ThrowHit);
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ use avian3d::prelude::*;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState, control::ControlState, loading_assets::UIAssets, physics_layers::GameLayer,
|
||||||
control::{ControlState, controller_common::PlayerMovement},
|
|
||||||
loading_assets::UIAssets,
|
|
||||||
physics_layers::GameLayer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Component, Reflect, Debug)]
|
#[derive(Component, Reflect, Debug)]
|
||||||
@@ -152,16 +149,9 @@ fn update(
|
|||||||
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
|
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_view(
|
fn rotate_view(controls: Res<ControlState>, mut cam: Single<&mut CameraRotationInput>) {
|
||||||
controls: Res<ControlState>,
|
|
||||||
mut cam: Single<&mut CameraRotationInput>,
|
|
||||||
movement: Res<PlayerMovement>,
|
|
||||||
) {
|
|
||||||
if !controls.view_mode {
|
if !controls.view_mode {
|
||||||
if movement.any_direction {
|
cam.x = 0.;
|
||||||
cam.x = 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
src/character.rs
125
src/character.rs
@@ -1,12 +1,27 @@
|
|||||||
use crate::{GameState, heads_database::HeadsDatabase, loading_assets::GameAssets};
|
use crate::{
|
||||||
use bevy::{ecs::system::SystemParam, platform::collections::HashMap, prelude::*};
|
GameState, heads_database::HeadsDatabase, loading_assets::GameAssets, utils::trail::Trail,
|
||||||
use std::time::Duration;
|
};
|
||||||
|
use bevy::{
|
||||||
|
ecs::system::SystemParam, platform::collections::HashMap, prelude::*, scene::SceneInstanceReady,
|
||||||
|
};
|
||||||
|
use std::{f32::consts::PI, time::Duration};
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct ProjectileOrigin;
|
pub struct ProjectileOrigin;
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct AnimatedCharacter(pub usize);
|
pub struct AnimatedCharacter {
|
||||||
|
head: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedCharacter {
|
||||||
|
pub fn new(head: usize) -> Self {
|
||||||
|
Self { head }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct AnimatedCharacterAsset(pub Handle<Gltf>);
|
||||||
|
|
||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
pub struct CharacterHierarchy<'w, 's> {
|
pub struct CharacterHierarchy<'w, 's> {
|
||||||
@@ -28,30 +43,31 @@ pub struct CharacterAnimations {
|
|||||||
pub idle: AnimationNodeIndex,
|
pub idle: AnimationNodeIndex,
|
||||||
pub run: AnimationNodeIndex,
|
pub run: AnimationNodeIndex,
|
||||||
pub jump: AnimationNodeIndex,
|
pub jump: AnimationNodeIndex,
|
||||||
|
pub shooting: Option<AnimationNodeIndex>,
|
||||||
pub graph: Handle<AnimationGraph>,
|
pub graph: Handle<AnimationGraph>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(spawn, setup_once_loaded, setup_projectile_origin).run_if(in_state(GameState::Playing)),
|
(spawn, setup_once_loaded).run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "dbg")]
|
#[cfg(feature = "dbg")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
debug_show_projectile_origin.run_if(in_state(GameState::Playing)),
|
debug_show_projectile_origin_and_trial.run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, &AnimatedCharacter), Added<AnimatedCharacter>>,
|
query: Query<(Entity, &AnimatedCharacter), Added<AnimatedCharacter>>,
|
||||||
gltf_assets: Res<Assets<Gltf>>,
|
gltf_assets: Res<Assets<Gltf>>,
|
||||||
assets: Res<GameAssets>,
|
assets: Res<GameAssets>,
|
||||||
heads_db: Res<HeadsDatabase>,
|
heads_db: Res<HeadsDatabase>,
|
||||||
) {
|
) {
|
||||||
for (entity, character) in &mut query {
|
for (entity, character) in query.iter() {
|
||||||
let key = heads_db.head_key(character.0);
|
let key = heads_db.head_key(character.head);
|
||||||
|
|
||||||
let handle = assets
|
let handle = assets
|
||||||
.characters
|
.characters
|
||||||
@@ -63,50 +79,82 @@ fn spawn(
|
|||||||
});
|
});
|
||||||
let asset = gltf_assets.get(handle).unwrap();
|
let asset = gltf_assets.get(handle).unwrap();
|
||||||
|
|
||||||
commands.entity(entity).insert((
|
let mut t =
|
||||||
Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2)),
|
Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2));
|
||||||
SceneRoot(asset.scenes[0].clone()),
|
|
||||||
));
|
t.rotate_y(PI);
|
||||||
|
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert((
|
||||||
|
t,
|
||||||
|
SceneRoot(asset.scenes[0].clone()),
|
||||||
|
AnimatedCharacterAsset(handle.clone()),
|
||||||
|
))
|
||||||
|
.observe(find_marker_bones);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_projectile_origin(
|
fn find_marker_bones(
|
||||||
|
trigger: Trigger<SceneInstanceReady>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
query: Query<Entity, Added<AnimationPlayer>>,
|
|
||||||
descendants: Query<&Children>,
|
descendants: Query<&Children>,
|
||||||
name: Query<&Name>,
|
name: Query<&Name>,
|
||||||
|
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
|
||||||
) {
|
) {
|
||||||
for character in query.iter() {
|
let entity = trigger.target();
|
||||||
for child in descendants.iter_descendants(character) {
|
|
||||||
let Ok(name) = name.get(child) else {
|
let mut origin_found = false;
|
||||||
continue;
|
for child in descendants.iter_descendants(entity) {
|
||||||
};
|
let Ok(name) = name.get(child) else {
|
||||||
if name.as_str() == "ProjectileOrigin" {
|
continue;
|
||||||
commands.entity(child).insert(ProjectileOrigin);
|
};
|
||||||
}
|
|
||||||
|
if name.as_str() == "ProjectileOrigin" {
|
||||||
|
commands.entity(child).insert(ProjectileOrigin);
|
||||||
|
origin_found = true;
|
||||||
|
} else if name.as_str().starts_with("Trail") {
|
||||||
|
commands.entity(child).insert((
|
||||||
|
Trail::new(
|
||||||
|
20,
|
||||||
|
LinearRgba::new(1., 1.0, 1., 0.5),
|
||||||
|
LinearRgba::new(1., 1., 1., 0.5),
|
||||||
|
),
|
||||||
|
Gizmo {
|
||||||
|
handle: gizmo_assets.add(GizmoAsset::default()),
|
||||||
|
line_config: GizmoLineConfig {
|
||||||
|
width: 24.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !origin_found {
|
||||||
|
error!("ProjectileOrigin not found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_once_loaded(
|
fn setup_once_loaded(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
|
mut query: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
|
||||||
parent: Query<&ChildOf>,
|
parent: Query<&ChildOf>,
|
||||||
animated_character: Query<&AnimatedCharacter>,
|
animated_character: Query<(&AnimatedCharacter, &AnimatedCharacterAsset)>,
|
||||||
assets: Res<GameAssets>,
|
|
||||||
gltf_assets: Res<Assets<Gltf>>,
|
gltf_assets: Res<Assets<Gltf>>,
|
||||||
mut graphs: ResMut<Assets<AnimationGraph>>,
|
mut graphs: ResMut<Assets<AnimationGraph>>,
|
||||||
) {
|
) {
|
||||||
for (entity, mut player) in &mut query {
|
for (entity, mut player) in &mut query {
|
||||||
let Some(_animated_character) = parent
|
let Some((_character, asset)) = parent
|
||||||
.iter_ancestors(entity)
|
.iter_ancestors(entity)
|
||||||
.find_map(|ancestor| animated_character.get(ancestor).ok())
|
.find_map(|ancestor| animated_character.get(ancestor).ok())
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, handle) = assets.characters.iter().next().unwrap();
|
let asset = gltf_assets.get(asset.0.id()).unwrap();
|
||||||
let asset = gltf_assets.get(handle).unwrap();
|
|
||||||
|
|
||||||
let animations = asset
|
let animations = asset
|
||||||
.named_animations
|
.named_animations
|
||||||
@@ -114,18 +162,29 @@ fn setup_once_loaded(
|
|||||||
.map(|(name, animation)| (name.to_string(), animation.clone()))
|
.map(|(name, animation)| (name.to_string(), animation.clone()))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let (graph, node_indices) = AnimationGraph::from_clips([
|
let mut clips = vec![
|
||||||
animations["idle"].clone(),
|
animations["idle"].clone(),
|
||||||
animations["run"].clone(),
|
animations["run"].clone(),
|
||||||
animations["jump"].clone(),
|
animations["jump"].clone(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
// // Insert a resource with the current scene information
|
if let Some(shooting_animation) = animations.get("shoot") {
|
||||||
|
clips.push(shooting_animation.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (graph, node_indices) = AnimationGraph::from_clips(clips);
|
||||||
|
|
||||||
|
// Insert a resource with the current scene information
|
||||||
let graph_handle = graphs.add(graph);
|
let graph_handle = graphs.add(graph);
|
||||||
let animations = CharacterAnimations {
|
let animations = CharacterAnimations {
|
||||||
idle: node_indices[0],
|
idle: node_indices[0],
|
||||||
run: node_indices[1],
|
run: node_indices[1],
|
||||||
jump: node_indices[2],
|
jump: node_indices[2],
|
||||||
|
shooting: if node_indices.len() == 4 {
|
||||||
|
Some(node_indices[3])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
graph: graph_handle.clone(),
|
graph: graph_handle.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,9 +201,9 @@ fn setup_once_loaded(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dbg")]
|
#[cfg(feature = "dbg")]
|
||||||
fn debug_show_projectile_origin(
|
fn debug_show_projectile_origin_and_trial(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
query: Query<&GlobalTransform, With<ProjectileOrigin>>,
|
query: Query<&GlobalTransform, Or<(With<ProjectileOrigin>, With<Trail>)>>,
|
||||||
) {
|
) {
|
||||||
for projectile_origin in query.iter() {
|
for projectile_origin in query.iter() {
|
||||||
gizmos.sphere(
|
gizmos.sphere(
|
||||||
|
|||||||
@@ -24,12 +24,8 @@ pub fn kinematic_controller_collisions(
|
|||||||
// Iterate through collisions and move the kinematic body to resolve penetration
|
// Iterate through collisions and move the kinematic body to resolve penetration
|
||||||
for contacts in collisions.iter() {
|
for contacts in collisions.iter() {
|
||||||
// Get the rigid body entities of the colliders (colliders could be children)
|
// Get the rigid body entities of the colliders (colliders could be children)
|
||||||
let Ok(
|
let Ok([&ColliderOf { body: rb1 }, &ColliderOf { body: rb2 }]) =
|
||||||
[
|
collider_rbs.get_many([contacts.collider1, contacts.collider2])
|
||||||
&ColliderOf { rigid_body: rb1 },
|
|
||||||
&ColliderOf { rigid_body: rb2 },
|
|
||||||
],
|
|
||||||
) = collider_rbs.get_many([contacts.entity1, contacts.entity2])
|
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ pub fn reset_upon_switch(
|
|||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct PlayerMovement {
|
pub struct PlayerMovement {
|
||||||
pub any_direction: bool,
|
pub any_direction: bool,
|
||||||
|
pub shooting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A marker component indicating that an entity is using a character controller.
|
/// A marker component indicating that an entity is using a character controller.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use bevy::prelude::*;
|
|||||||
use crate::player::PlayerBodyMesh;
|
use crate::player::PlayerBodyMesh;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ControlState, ControllerSet, Controls,
|
ControlState, ControllerSet,
|
||||||
controller_common::{JumpImpulse, MovementDampingFactor, MovementSpeed, PlayerMovement},
|
controller_common::{MovementDampingFactor, MovementSpeed},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CharacterControllerPlugin;
|
pub struct CharacterControllerPlugin;
|
||||||
@@ -16,23 +16,13 @@ impl Plugin for CharacterControllerPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(rotate_rig, movement, apply_movement_damping)
|
||||||
set_movement_flag,
|
|
||||||
rotate_rig,
|
|
||||||
movement,
|
|
||||||
apply_movement_damping,
|
|
||||||
)
|
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(ControllerSet::ApplyControlsFly), // todo only in GameState::Playing?
|
.in_set(ControllerSet::ApplyControlsFly), // todo only in GameState::Playing?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
|
||||||
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>) {
|
|
||||||
player_movement.any_direction = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rotate_rig(
|
fn rotate_rig(
|
||||||
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
||||||
controls: Res<ControlState>,
|
controls: Res<ControlState>,
|
||||||
@@ -68,18 +58,13 @@ fn rotate_rig(
|
|||||||
|
|
||||||
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
|
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
|
||||||
fn movement(
|
fn movement(
|
||||||
controls: Res<Controls>,
|
mut controllers: Query<(&MovementSpeed, &mut LinearVelocity)>,
|
||||||
mut controllers: Query<(&MovementSpeed, &JumpImpulse, &mut LinearVelocity)>,
|
|
||||||
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||||
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
let move_dir = Vec2::new(0.0, 1.0); // Always full-throttle forwards when flying
|
let move_dir = Vec2::new(0.0, 70.) * time.delta_secs();
|
||||||
let mut jump_requested = controls.keyboard_state.jump;
|
|
||||||
|
|
||||||
if let Some(gamepad) = controls.gamepad_state {
|
for (movement_acceleration, mut linear_velocity) in &mut controllers {
|
||||||
jump_requested |= gamepad.jump;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (movement_acceleration, jump_impulse, mut linear_velocity) in &mut controllers {
|
|
||||||
let mut direction = move_dir.extend(0.0).xzy();
|
let mut direction = move_dir.extend(0.0).xzy();
|
||||||
|
|
||||||
if let Some(ref rig_transform) = rig_transform_q {
|
if let Some(ref rig_transform) = rig_transform_q {
|
||||||
@@ -88,10 +73,6 @@ fn movement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
linear_velocity.0 = -direction * movement_acceleration.0;
|
linear_velocity.0 = -direction * movement_acceleration.0;
|
||||||
|
|
||||||
if jump_requested {
|
|
||||||
linear_velocity.y = jump_impulse.0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{ControlState, ControllerSet, Controls, controller_common::MovementSpeedFactor};
|
use super::{ControlState, ControllerSet, Controls, controller_common::MovementSpeedFactor};
|
||||||
use crate::{GameState, player::PlayerBodyMesh};
|
use crate::{GameState, abilities::TriggerStateRes, player::PlayerBodyMesh};
|
||||||
use avian3d::{math::*, prelude::*};
|
use avian3d::{math::*, prelude::*};
|
||||||
use bevy::{ecs::query::Has, prelude::*};
|
use bevy::{ecs::query::Has, prelude::*};
|
||||||
|
|
||||||
@@ -59,7 +59,11 @@ fn update_grounded(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
||||||
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>, controls: Res<Controls>) {
|
fn set_movement_flag(
|
||||||
|
mut player_movement: ResMut<PlayerMovement>,
|
||||||
|
controls: Res<Controls>,
|
||||||
|
trigger: Res<TriggerStateRes>,
|
||||||
|
) {
|
||||||
let mut direction = controls.keyboard_state.move_dir;
|
let mut direction = controls.keyboard_state.move_dir;
|
||||||
let deadzone = 0.2;
|
let deadzone = 0.2;
|
||||||
|
|
||||||
@@ -74,6 +78,10 @@ fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>, controls: Res<
|
|||||||
} else if direction.length_squared() > deadzone {
|
} else if direction.length_squared() > deadzone {
|
||||||
player_movement.any_direction = true;
|
player_movement.any_direction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if player_movement.shooting != trigger.is_active() {
|
||||||
|
player_movement.shooting = trigger.is_active();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_view(
|
fn rotate_view(
|
||||||
|
|||||||
@@ -8,10 +8,16 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.add_systems(Startup, setup);
|
app.add_systems(Startup, setup);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(mut commands: Commands, keyboard: Res<ButtonInput<KeyCode>>) {
|
fn update(mut commands: Commands, keyboard: Res<ButtonInput<KeyCode>>, gamepads: Query<&Gamepad>) {
|
||||||
if keyboard.just_pressed(KeyCode::Backquote) {
|
if keyboard.just_pressed(KeyCode::Backquote) {
|
||||||
commands.trigger(LogViewerVisibility::Toggle);
|
commands.trigger(LogViewerVisibility::Toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for g in gamepads.iter() {
|
||||||
|
if g.just_pressed(GamepadButton::North) {
|
||||||
|
commands.trigger(LogViewerVisibility::Toggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
GameState, billboards::Billboard, global_observer, loading_assets::GameAssets, player::Player,
|
GameState, billboards::Billboard, global_observer, loading_assets::GameAssets,
|
||||||
sounds::PlaySound, squish_animation::SquishAnimation,
|
physics_layers::GameLayer, player::Player, sounds::PlaySound,
|
||||||
|
squish_animation::SquishAnimation, tb_entities::SecretHead,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -13,15 +14,50 @@ pub struct HeadDrops(pub Vec3, pub usize);
|
|||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
struct HeadDrop(pub usize);
|
struct HeadDrop(pub usize);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct SecretHeadMarker;
|
||||||
|
|
||||||
#[derive(Event, Reflect)]
|
#[derive(Event, Reflect)]
|
||||||
pub struct HeadCollected(pub usize);
|
pub struct HeadCollected(pub usize);
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_systems(Update, collect_head.run_if(in_state(GameState::Playing)));
|
app.add_systems(Update, collect_head.run_if(in_state(GameState::Playing)));
|
||||||
|
app.add_systems(OnEnter(GameState::Playing), spawn);
|
||||||
|
|
||||||
global_observer!(app, on_head_drop);
|
global_observer!(app, on_head_drop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<(Entity, &GlobalTransform, &SecretHead)>,
|
||||||
|
assets: Res<GameAssets>,
|
||||||
|
) {
|
||||||
|
for (e, t, head) in query {
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
Name::new("headdrop"),
|
||||||
|
SecretHeadMarker,
|
||||||
|
HeadDrop(head.head_id),
|
||||||
|
Transform::from_translation(t.translation() + Vec3::new(0., 2., 0.)),
|
||||||
|
Visibility::default(),
|
||||||
|
Collider::sphere(1.5),
|
||||||
|
LockedAxes::ROTATION_LOCKED,
|
||||||
|
RigidBody::Dynamic,
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Collectibles.to_bits()), LayerMask::ALL),
|
||||||
|
CollisionEventsEnabled,
|
||||||
|
Restitution::new(0.6),
|
||||||
|
))
|
||||||
|
.with_child((
|
||||||
|
Billboard,
|
||||||
|
SquishAnimation(2.6),
|
||||||
|
SceneRoot(assets.mesh_head_drop.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
commands.entity(e).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_head_drop(trigger: Trigger<HeadDrops>, mut commands: Commands, assets: Res<GameAssets>) {
|
fn on_head_drop(trigger: Trigger<HeadDrops>, mut commands: Commands, assets: Res<GameAssets>) {
|
||||||
let HeadDrops(position, id) = trigger.event();
|
let HeadDrops(position, id) = trigger.event();
|
||||||
|
|
||||||
@@ -40,6 +76,7 @@ fn on_head_drop(trigger: Trigger<HeadDrops>, mut commands: Commands, assets: Res
|
|||||||
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
|
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
|
||||||
LockedAxes::ROTATION_LOCKED,
|
LockedAxes::ROTATION_LOCKED,
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Collectibles.to_bits()), LayerMask::ALL),
|
||||||
CollisionEventsEnabled,
|
CollisionEventsEnabled,
|
||||||
Restitution::new(0.6),
|
Restitution::new(0.6),
|
||||||
))
|
))
|
||||||
@@ -55,6 +92,7 @@ fn collect_head(
|
|||||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||||
query_player: Query<&Player>,
|
query_player: Query<&Player>,
|
||||||
query_collectable: Query<&HeadDrop>,
|
query_collectable: Query<&HeadDrop>,
|
||||||
|
query_secret: Query<&SecretHeadMarker>,
|
||||||
) {
|
) {
|
||||||
for CollisionStarted(e1, e2) in collision_event_reader.read() {
|
for CollisionStarted(e1, e2) in collision_event_reader.read() {
|
||||||
let collectable = if query_player.contains(*e1) && query_collectable.contains(*e2) {
|
let collectable = if query_player.contains(*e1) && query_collectable.contains(*e2) {
|
||||||
@@ -67,7 +105,13 @@ fn collect_head(
|
|||||||
|
|
||||||
let key = query_collectable.get(collectable).unwrap();
|
let key = query_collectable.get(collectable).unwrap();
|
||||||
|
|
||||||
commands.trigger(PlaySound::HeadCollect);
|
let is_secret = query_secret.contains(collectable);
|
||||||
|
|
||||||
|
if is_secret {
|
||||||
|
commands.trigger(PlaySound::SecretHeadCollect);
|
||||||
|
} else {
|
||||||
|
commands.trigger(PlaySound::HeadCollect);
|
||||||
|
}
|
||||||
commands.trigger(HeadCollected(key.0));
|
commands.trigger(HeadCollected(key.0));
|
||||||
commands.entity(collectable).despawn();
|
commands.entity(collectable).despawn();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,39 @@ impl ActiveHeads {
|
|||||||
head.ammo = head.ammo.saturating_sub(1);
|
head.ammo = head.ammo.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn medic_heal(&mut self, heal_amount: u32, time: f32) -> Option<u32> {
|
||||||
|
let mut healed = false;
|
||||||
|
for (index, head) in self.heads.iter_mut().enumerate() {
|
||||||
|
if index == self.current_slot {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(head) = head {
|
||||||
|
if head.health < head.health_max {
|
||||||
|
head.health = head
|
||||||
|
.health
|
||||||
|
.saturating_add(heal_amount)
|
||||||
|
.clamp(0, head.health_max);
|
||||||
|
healed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if healed {
|
||||||
|
let Some(head) = &mut self.heads[self.current_slot] else {
|
||||||
|
error!("cannot heal with empty head");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
head.last_use = time;
|
||||||
|
head.health = head.health.saturating_sub(1);
|
||||||
|
|
||||||
|
Some(head.health)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn slot(&self) -> usize {
|
pub fn slot(&self) -> usize {
|
||||||
self.current_slot
|
self.current_slot
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ pub struct HeadStats {
|
|||||||
pub ammo: u32,
|
pub ammo: u32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub damage: u32,
|
pub damage: u32,
|
||||||
|
// ability per second
|
||||||
|
#[serde(default = "default_aps")]
|
||||||
|
pub aps: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_aps() -> f32 {
|
||||||
|
1.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_ammo() -> u32 {
|
fn default_ammo() -> u32 {
|
||||||
|
|||||||
99
src/heal_effect.rs
Normal file
99
src/heal_effect.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use crate::{
|
||||||
|
GameState, abilities::Healing, loading_assets::GameAssets, utils::billboards::Billboard,
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use rand::{Rng, thread_rng};
|
||||||
|
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
#[require(Transform, InheritedVisibility)]
|
||||||
|
struct HealParticleEffect {
|
||||||
|
next_spawn: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct HealParticle {
|
||||||
|
start_scale: f32,
|
||||||
|
end_scale: f32,
|
||||||
|
start_pos: Vec3,
|
||||||
|
end_pos: Vec3,
|
||||||
|
start_time: f32,
|
||||||
|
life_time: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plugin(app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
(on_added, update_effects, update_particles).run_if(in_state(GameState::Playing)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_added(mut cmds: Commands, query: Query<&Healing, Added<Healing>>) {
|
||||||
|
for healing in query.iter() {
|
||||||
|
cmds.entity(healing.0).insert((
|
||||||
|
Name::new("heal-particle-effect"),
|
||||||
|
HealParticleEffect::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_effects(
|
||||||
|
mut cmds: Commands,
|
||||||
|
mut query: Query<(&mut HealParticleEffect, Entity)>,
|
||||||
|
time: Res<Time>,
|
||||||
|
assets: Res<GameAssets>,
|
||||||
|
) {
|
||||||
|
const DISTANCE: f32 = 4.;
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
let now = time.elapsed_secs();
|
||||||
|
for (mut effect, e) in query.iter_mut() {
|
||||||
|
if effect.next_spawn < now {
|
||||||
|
let start_pos = Vec3::new(
|
||||||
|
rng.gen_range(-DISTANCE..DISTANCE),
|
||||||
|
2.,
|
||||||
|
rng.gen_range(-DISTANCE..DISTANCE),
|
||||||
|
);
|
||||||
|
let max_distance = start_pos.length().max(0.8);
|
||||||
|
let end_pos =
|
||||||
|
start_pos + (start_pos.normalize() * -1.) * rng.gen_range(0.5..max_distance);
|
||||||
|
let start_scale = rng.gen_range(0.7..1.0);
|
||||||
|
let end_scale = rng.gen_range(0.1..start_scale);
|
||||||
|
|
||||||
|
cmds.entity(e).with_child((
|
||||||
|
Name::new("heal-particle"),
|
||||||
|
SceneRoot(assets.mesh_heal_particle.clone()),
|
||||||
|
Billboard,
|
||||||
|
Transform::from_translation(start_pos),
|
||||||
|
HealParticle {
|
||||||
|
start_scale,
|
||||||
|
end_scale,
|
||||||
|
start_pos,
|
||||||
|
end_pos,
|
||||||
|
start_time: now,
|
||||||
|
life_time: rng.gen_range(0.3..1.0),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
effect.next_spawn = now + rng.gen_range(0.1..0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_particles(
|
||||||
|
mut cmds: Commands,
|
||||||
|
mut query: Query<(&mut Transform, &HealParticle, Entity)>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (mut transform, particle, e) in query.iter_mut() {
|
||||||
|
if particle.start_time + particle.life_time < time.elapsed_secs() {
|
||||||
|
cmds.entity(e).despawn();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = (time.elapsed_secs() - particle.start_time) / particle.life_time;
|
||||||
|
|
||||||
|
// info!("particle[{e:?}] t: {t}");
|
||||||
|
transform.translation = particle.start_pos.lerp(particle.end_pos, t);
|
||||||
|
transform.scale = Vec3::splat(particle.start_scale.lerp(particle.end_scale, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
GameState, billboards::Billboard, global_observer, loading_assets::GameAssets, player::Player,
|
GameState, billboards::Billboard, global_observer, loading_assets::GameAssets,
|
||||||
sounds::PlaySound, squish_animation::SquishAnimation,
|
physics_layers::GameLayer, player::Player, sounds::PlaySound,
|
||||||
|
squish_animation::SquishAnimation,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -37,6 +38,7 @@ fn on_spawn_key(trigger: Trigger<KeySpawn>, mut commands: Commands, assets: Res<
|
|||||||
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
|
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
|
||||||
LockedAxes::ROTATION_LOCKED,
|
LockedAxes::ROTATION_LOCKED,
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Collectibles.to_bits()), LayerMask::ALL),
|
||||||
CollisionEventsEnabled,
|
CollisionEventsEnabled,
|
||||||
Restitution::new(0.6),
|
Restitution::new(0.6),
|
||||||
Children::spawn(Spawn((
|
Children::spawn(Spawn((
|
||||||
|
|||||||
@@ -9,18 +9,14 @@ use bevy_asset_loader::prelude::*;
|
|||||||
pub struct AudioAssets {
|
pub struct AudioAssets {
|
||||||
#[asset(path = "sfx/music/02.ogg")]
|
#[asset(path = "sfx/music/02.ogg")]
|
||||||
pub music: Handle<AudioSource>,
|
pub music: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/ambient/downtown.ogg")]
|
#[asset(path = "sfx/ambient/downtown_loop.ogg")]
|
||||||
pub ambient: Handle<AudioSource>,
|
pub ambient: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/effects/key_collect.ogg")]
|
#[asset(path = "sfx/effects/key_collect.ogg")]
|
||||||
pub key_collect: Handle<AudioSource>,
|
pub key_collect: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/abilities/gun.ogg")]
|
|
||||||
pub gun: Handle<AudioSource>,
|
|
||||||
#[asset(path = "sfx/abilities/crossbow.ogg")]
|
|
||||||
pub crossbow: Handle<AudioSource>,
|
|
||||||
#[asset(path = "sfx/effects/gate.ogg")]
|
#[asset(path = "sfx/effects/gate.ogg")]
|
||||||
pub gate: Handle<AudioSource>,
|
pub gate: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/effects/cash.ogg")]
|
#[asset(path = "sfx/effects/cash_collect.ogg")]
|
||||||
pub cash: Handle<AudioSource>,
|
pub cash_collect: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/ui/selection.ogg")]
|
#[asset(path = "sfx/ui/selection.ogg")]
|
||||||
pub selection: Handle<AudioSource>,
|
pub selection: Handle<AudioSource>,
|
||||||
|
|
||||||
@@ -38,6 +34,15 @@ pub struct AudioAssets {
|
|||||||
pub throw_explosion: Handle<AudioSource>,
|
pub throw_explosion: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/abilities/jet.ogg")]
|
#[asset(path = "sfx/abilities/jet.ogg")]
|
||||||
pub jet: Handle<AudioSource>,
|
pub jet: Handle<AudioSource>,
|
||||||
|
#[asset(path = "sfx/abilities/gun.ogg")]
|
||||||
|
pub gun: Handle<AudioSource>,
|
||||||
|
#[asset(path = "sfx/abilities/crossbow.ogg")]
|
||||||
|
pub crossbow: Handle<AudioSource>,
|
||||||
|
#[asset(path = "sfx/abilities/heal.ogg")]
|
||||||
|
pub healing: Handle<AudioSource>,
|
||||||
|
#[asset(path = "sfx/abilities/missile-explosion.ogg")]
|
||||||
|
pub missile_explosion: Handle<AudioSource>,
|
||||||
|
|
||||||
#[asset(path = "sfx/ui/backpack_open.ogg")]
|
#[asset(path = "sfx/ui/backpack_open.ogg")]
|
||||||
pub backpack_open: Handle<AudioSource>,
|
pub backpack_open: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/ui/backpack_close.ogg")]
|
#[asset(path = "sfx/ui/backpack_close.ogg")]
|
||||||
@@ -45,12 +50,11 @@ pub struct AudioAssets {
|
|||||||
|
|
||||||
#[asset(path = "sfx/effects/head_collect.ogg")]
|
#[asset(path = "sfx/effects/head_collect.ogg")]
|
||||||
pub head_collect: Handle<AudioSource>,
|
pub head_collect: Handle<AudioSource>,
|
||||||
|
#[asset(path = "sfx/effects/secret_collected.ogg")]
|
||||||
|
pub secret_head_collect: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/effects/head_drop.ogg")]
|
#[asset(path = "sfx/effects/head_drop.ogg")]
|
||||||
pub head_drop: Handle<AudioSource>,
|
pub head_drop: Handle<AudioSource>,
|
||||||
|
|
||||||
#[asset(path = "sfx/abilities/missile-explosion.ogg")]
|
|
||||||
pub missile_explosion: Handle<AudioSource>,
|
|
||||||
|
|
||||||
#[asset(path = "sfx/hit", collection(typed))]
|
#[asset(path = "sfx/hit", collection(typed))]
|
||||||
pub hit: Vec<Handle<AudioSource>>,
|
pub hit: Vec<Handle<AudioSource>>,
|
||||||
#[asset(path = "sfx/heads", collection(mapped, typed))]
|
#[asset(path = "sfx/heads", collection(mapped, typed))]
|
||||||
@@ -98,6 +102,9 @@ pub struct GameAssets {
|
|||||||
#[asset(path = "models/cash.glb#Scene0")]
|
#[asset(path = "models/cash.glb#Scene0")]
|
||||||
pub mesh_cash: Handle<Scene>,
|
pub mesh_cash: Handle<Scene>,
|
||||||
|
|
||||||
|
#[asset(path = "models/medic_particle.glb#Scene0")]
|
||||||
|
pub mesh_heal_particle: Handle<Scene>,
|
||||||
|
|
||||||
#[asset(path = "models/projectiles", collection(mapped, typed))]
|
#[asset(path = "models/projectiles", collection(mapped, typed))]
|
||||||
pub projectiles: HashMap<AssetFileName, Handle<Gltf>>,
|
pub projectiles: HashMap<AssetFileName, Handle<Gltf>>,
|
||||||
|
|
||||||
|
|||||||
65
src/main.rs
65
src/main.rs
@@ -14,6 +14,7 @@ mod head;
|
|||||||
mod head_drop;
|
mod head_drop;
|
||||||
mod heads;
|
mod heads;
|
||||||
mod heads_database;
|
mod heads_database;
|
||||||
|
mod heal_effect;
|
||||||
mod hitpoints;
|
mod hitpoints;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod loading_assets;
|
mod loading_assets;
|
||||||
@@ -37,7 +38,7 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
use bevy_common_assets::ron::RonAssetPlugin;
|
use bevy_common_assets::ron::RonAssetPlugin;
|
||||||
use bevy_sprite3d::Sprite3dPlugin;
|
use bevy_sprite3d::Sprite3dPlugin;
|
||||||
use bevy_steamworks::{Client, FriendFlags, SteamworksPlugin};
|
use bevy_steamworks::{Client, FriendFlags, SteamworksEvent, SteamworksPlugin};
|
||||||
use bevy_trenchbroom::prelude::*;
|
use bevy_trenchbroom::prelude::*;
|
||||||
use bevy_ui_gradients::UiGradientsPlugin;
|
use bevy_ui_gradients::UiGradientsPlugin;
|
||||||
use camera::MainCamera;
|
use camera::MainCamera;
|
||||||
@@ -81,7 +82,7 @@ fn main() {
|
|||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
title: "HEDZ Reloaded".into(),
|
title: "HEDZ Reloaded".into(),
|
||||||
resolution: (1024., 768.).into(),
|
// resolution: (1024., 768.).into(),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
@@ -94,7 +95,15 @@ fn main() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
match SteamworksPlugin::init_app(1603000) {
|
let app_id = 1603000;
|
||||||
|
// should only be done in production builds
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
if steamworks::restart_app_if_necessary(app_id.into()) {
|
||||||
|
info!("Restarting app via steam");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match SteamworksPlugin::init_app(app_id) {
|
||||||
Ok(plugin) => {
|
Ok(plugin) => {
|
||||||
app.add_plugins(plugin);
|
app.add_plugins(plugin);
|
||||||
}
|
}
|
||||||
@@ -103,10 +112,15 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.add_plugins(bevy_debug_log::LogViewerPlugin::default());
|
app.add_plugins(
|
||||||
|
bevy_debug_log::LogViewerPlugin::default()
|
||||||
|
.auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
|
||||||
|
);
|
||||||
app.add_plugins(PhysicsPlugins::default());
|
app.add_plugins(PhysicsPlugins::default());
|
||||||
app.add_plugins(Sprite3dPlugin);
|
app.add_plugins(Sprite3dPlugin);
|
||||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
app.add_plugins(TrenchBroomPlugins(
|
||||||
|
TrenchBroomConfig::new("hedz").icon(None),
|
||||||
|
));
|
||||||
app.add_plugins(UiGradientsPlugin);
|
app.add_plugins(UiGradientsPlugin);
|
||||||
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
|
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
|
||||||
|
|
||||||
@@ -154,6 +168,8 @@ fn main() {
|
|||||||
app.add_plugins(water::plugin);
|
app.add_plugins(water::plugin);
|
||||||
app.add_plugins(head_drop::plugin);
|
app.add_plugins(head_drop::plugin);
|
||||||
app.add_plugins(trail::plugin);
|
app.add_plugins(trail::plugin);
|
||||||
|
app.add_plugins(heal_effect::plugin);
|
||||||
|
app.add_plugins(tb_entities::plugin);
|
||||||
|
|
||||||
app.init_state::<GameState>();
|
app.init_state::<GameState>();
|
||||||
|
|
||||||
@@ -170,7 +186,9 @@ fn main() {
|
|||||||
Startup,
|
Startup,
|
||||||
(
|
(
|
||||||
write_trenchbroom_config,
|
write_trenchbroom_config,
|
||||||
steam_system.run_if(resource_exists::<Client>),
|
(steam_system, steam_events)
|
||||||
|
.chain()
|
||||||
|
.run_if(resource_exists::<Client>),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
app.add_systems(OnEnter(GameState::Playing), music);
|
app.add_systems(OnEnter(GameState::Playing), music);
|
||||||
@@ -179,7 +197,35 @@ fn main() {
|
|||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn steam_events(mut events: EventReader<SteamworksEvent>) {
|
||||||
|
for e in events.read() {
|
||||||
|
info!("steam ev: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn steam_system(steam_client: Res<Client>) {
|
fn steam_system(steam_client: Res<Client>) {
|
||||||
|
steam_client.matchmaking().request_lobby_list(|list| {
|
||||||
|
let Ok(list) = list else { return };
|
||||||
|
|
||||||
|
info!("lobby list: [{}]", list.len());
|
||||||
|
for (i, l) in list.iter().enumerate() {
|
||||||
|
info!("lobby [{i}]: {:?}", l);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
steam_client
|
||||||
|
.matchmaking()
|
||||||
|
.create_lobby(
|
||||||
|
steamworks::LobbyType::FriendsOnly,
|
||||||
|
4,
|
||||||
|
|result| match result {
|
||||||
|
Ok(lobby_id) => {
|
||||||
|
info!("Created lobby with ID: {:?}", lobby_id);
|
||||||
|
}
|
||||||
|
Err(e) => error!("Failed to create lobby: {}", e),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
|
for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
|
||||||
info!(
|
info!(
|
||||||
"Steam Friend: {:?} - {}({:?})",
|
"Steam Friend: {:?} - {}({:?})",
|
||||||
@@ -233,8 +279,11 @@ fn music(assets: Res<AudioAssets>, mut commands: Commands) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_trenchbroom_config(server: Res<TrenchBroomServer>) {
|
fn write_trenchbroom_config(server: Res<TrenchBroomServer>, type_registry: Res<AppTypeRegistry>) {
|
||||||
if let Err(e) = server.config.write_folder("trenchbroom/hedz") {
|
if let Err(e) = server
|
||||||
|
.config
|
||||||
|
.write_game_config("trenchbroom/hedz", &type_registry.read())
|
||||||
|
{
|
||||||
warn!("Failed to write trenchbroom config: {}", e);
|
warn!("Failed to write trenchbroom config: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: R
|
|||||||
]),
|
]),
|
||||||
))
|
))
|
||||||
.insert_if(Ai, || !spawn.disable_ai)
|
.insert_if(Ai, || !spawn.disable_ai)
|
||||||
.with_child((Name::from("body-rig"), AnimatedCharacter(id)))
|
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
||||||
.observe(on_kill);
|
.observe(on_kill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ pub enum GameLayer {
|
|||||||
Player,
|
Player,
|
||||||
Npc,
|
Npc,
|
||||||
Projectile,
|
Projectile,
|
||||||
|
Collectibles,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ fn init(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let target = Vec3::new(transform.translation.x, target.y, transform.translation.z);
|
|
||||||
let platform = ActivePlatform {
|
let platform = ActivePlatform {
|
||||||
start: transform.translation,
|
start: transform.translation,
|
||||||
target,
|
target,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
window::{CursorGrabMode, PrimaryWindow},
|
window::{CursorGrabMode, PrimaryWindow},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
@@ -30,6 +29,7 @@ pub struct Player;
|
|||||||
struct PlayerAnimations;
|
struct PlayerAnimations;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
|
#[require(Transform, Visibility)]
|
||||||
pub struct PlayerBodyMesh;
|
pub struct PlayerBodyMesh;
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
@@ -84,11 +84,9 @@ fn spawn(
|
|||||||
CharacterControllerBundle::new(collider, gravity),
|
CharacterControllerBundle::new(collider, gravity),
|
||||||
children![(
|
children![(
|
||||||
Name::new("player-rig"),
|
Name::new("player-rig"),
|
||||||
Transform::default(),
|
|
||||||
Visibility::default(),
|
|
||||||
PlayerBodyMesh,
|
PlayerBodyMesh,
|
||||||
CameraArmRotation,
|
CameraArmRotation,
|
||||||
children![AnimatedCharacter(0)]
|
children![AnimatedCharacter::new(0)]
|
||||||
)],
|
)],
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -181,9 +179,21 @@ fn toggle_animation(
|
|||||||
animations.idle
|
animations.idle
|
||||||
};
|
};
|
||||||
|
|
||||||
transition
|
let (mut index, mut duration) = (index, Duration::from_millis(100));
|
||||||
.play(&mut player, index, Duration::from_millis(100))
|
|
||||||
.repeat();
|
if movement.shooting {
|
||||||
|
if let Some(shoot_anim) = animations.shooting {
|
||||||
|
(index, duration) = (shoot_anim, Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if transition
|
||||||
|
.get_main_animation()
|
||||||
|
.map(|playing| playing != index)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
transition.play(&mut player, index, duration).repeat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +218,7 @@ fn on_update_head(
|
|||||||
|
|
||||||
commands
|
commands
|
||||||
.entity(body_mesh)
|
.entity(body_mesh)
|
||||||
.with_child(AnimatedCharacter(trigger.0));
|
.with_child(AnimatedCharacter::new(trigger.0));
|
||||||
|
|
||||||
//TODO: make part of full character mesh later
|
//TODO: make part of full character mesh later
|
||||||
if head_db.head_stats(trigger.0).controls == HeadControls::Plane {
|
if head_db.head_stats(trigger.0).controls == HeadControls::Plane {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub enum PlaySound {
|
|||||||
Gate,
|
Gate,
|
||||||
CashCollect,
|
CashCollect,
|
||||||
HeadCollect,
|
HeadCollect,
|
||||||
|
SecretHeadCollect,
|
||||||
HeadDrop,
|
HeadDrop,
|
||||||
Selection,
|
Selection,
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -48,7 +49,7 @@ fn on_spawn_sounds(
|
|||||||
PlaySound::Gun => assets.gun.clone(),
|
PlaySound::Gun => assets.gun.clone(),
|
||||||
PlaySound::Crossbow => assets.crossbow.clone(),
|
PlaySound::Crossbow => assets.crossbow.clone(),
|
||||||
PlaySound::Gate => assets.gate.clone(),
|
PlaySound::Gate => assets.gate.clone(),
|
||||||
PlaySound::CashCollect => assets.cash.clone(),
|
PlaySound::CashCollect => assets.cash_collect.clone(),
|
||||||
PlaySound::Selection => assets.selection.clone(),
|
PlaySound::Selection => assets.selection.clone(),
|
||||||
PlaySound::Throw => assets.throw.clone(),
|
PlaySound::Throw => assets.throw.clone(),
|
||||||
PlaySound::ThrowHit => assets.throw_explosion.clone(),
|
PlaySound::ThrowHit => assets.throw_explosion.clone(),
|
||||||
@@ -57,6 +58,7 @@ fn on_spawn_sounds(
|
|||||||
PlaySound::CashHeal => assets.cash_heal.clone(),
|
PlaySound::CashHeal => assets.cash_heal.clone(),
|
||||||
PlaySound::HeadDrop => assets.head_drop.clone(),
|
PlaySound::HeadDrop => assets.head_drop.clone(),
|
||||||
PlaySound::HeadCollect => assets.head_collect.clone(),
|
PlaySound::HeadCollect => assets.head_collect.clone(),
|
||||||
|
PlaySound::SecretHeadCollect => assets.secret_head_collect.clone(),
|
||||||
PlaySound::MissileExplosion => assets.missile_explosion.clone(),
|
PlaySound::MissileExplosion => assets.missile_explosion.clone(),
|
||||||
PlaySound::Backpack { open } => {
|
PlaySound::Backpack { open } => {
|
||||||
if *open {
|
if *open {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use crate::loading_assets::GameAssets;
|
|||||||
use crate::physics_layers::GameLayer;
|
use crate::physics_layers::GameLayer;
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
#[component(on_add = Self::on_add)]
|
#[component(on_add = Self::on_add)]
|
||||||
#[model({ "path": "models/spawn.glb" })]
|
#[model({ "path": "models/spawn.glb" })]
|
||||||
pub struct SpawnPoint {}
|
pub struct SpawnPoint {}
|
||||||
@@ -35,80 +35,81 @@ impl SpawnPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[geometry(GeometryProvider::new().convex_collider())]
|
#[spawn_hooks(SpawnHooks::new().convex_collider())]
|
||||||
pub struct Worldspawn;
|
pub struct Worldspawn;
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[geometry(GeometryProvider::new())]
|
#[spawn_hooks(SpawnHooks::new())]
|
||||||
|
#[base(Transform)]
|
||||||
pub struct Water;
|
pub struct Water;
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
#[geometry(GeometryProvider::new().convex_collider())]
|
#[spawn_hooks(SpawnHooks::new().convex_collider())]
|
||||||
pub struct Crates;
|
pub struct Crates;
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
#[geometry(GeometryProvider::new().convex_collider())]
|
#[spawn_hooks(SpawnHooks::new().convex_collider())]
|
||||||
pub struct NamedEntity {
|
pub struct NamedEntity {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform, Target)]
|
#[base(Transform, Target)]
|
||||||
#[geometry(GeometryProvider::new().convex_collider())]
|
#[spawn_hooks(SpawnHooks::new().convex_collider())]
|
||||||
pub struct Platform;
|
pub struct Platform;
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
pub struct PlatformTarget {
|
pub struct PlatformTarget {
|
||||||
pub targetname: String,
|
pub targetname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SolidClass, Component, Reflect, Default)]
|
#[derive(SolidClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform, Target)]
|
#[base(Transform, Target)]
|
||||||
#[geometry(GeometryProvider::new().convex_collider())]
|
#[spawn_hooks(SpawnHooks::new().convex_collider())]
|
||||||
pub struct Movable {
|
pub struct Movable {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
pub struct MoveTarget {
|
pub struct MoveTarget {
|
||||||
pub targetname: String,
|
pub targetname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
pub struct CameraTarget {
|
pub struct CameraTarget {
|
||||||
pub targetname: String,
|
pub targetname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform, Target)]
|
#[base(Transform, Target)]
|
||||||
pub struct CutsceneCamera {
|
pub struct CutsceneCamera {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub targetname: String,
|
pub targetname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform, Target)]
|
#[base(Transform, Target)]
|
||||||
pub struct CutsceneCameraMovementEnd;
|
pub struct CutsceneCameraMovementEnd;
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
#[component(on_add = Self::on_add)]
|
#[component(on_add = Self::on_add)]
|
||||||
#[model({ "path": "models/alien_naked.glb" })]
|
#[model({ "path": "models/alien_naked.glb" })]
|
||||||
pub struct EnemySpawn {
|
pub struct EnemySpawn {
|
||||||
@@ -132,7 +133,7 @@ impl EnemySpawn {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut this_transform = *this_transform;
|
let mut this_transform = *this_transform;
|
||||||
this_transform.translation += Vec3::new(0., 1., 0.);
|
this_transform.translation += Vec3::new(0., 1.5, 0.);
|
||||||
|
|
||||||
let head = this.head.clone();
|
let head = this.head.clone();
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ impl EnemySpawn {
|
|||||||
Name::from(format!("enemy [{}]", head)),
|
Name::from(format!("enemy [{}]", head)),
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
Collider::capsule(0.4, 2.),
|
Collider::capsule(0.6, 2.),
|
||||||
CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL),
|
CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL),
|
||||||
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
|
LockedAxes::new().lock_rotation_z().lock_rotation_x(),
|
||||||
));
|
));
|
||||||
@@ -149,8 +150,8 @@ impl EnemySpawn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PointClass, Component, Reflect, Default)]
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(QuakeClass, Component)]
|
||||||
#[require(Transform)]
|
#[base(Transform)]
|
||||||
#[component(on_add = Self::on_add)]
|
#[component(on_add = Self::on_add)]
|
||||||
#[model({ "path": "models/cash.glb" })]
|
#[model({ "path": "models/cash.glb" })]
|
||||||
pub struct CashSpawn {}
|
pub struct CashSpawn {}
|
||||||
@@ -168,8 +169,35 @@ impl CashSpawn {
|
|||||||
SceneRoot(mesh),
|
SceneRoot(mesh),
|
||||||
Cash,
|
Cash,
|
||||||
Collider::cuboid(2., 3.0, 2.),
|
Collider::cuboid(2., 3.0, 2.),
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Collectibles.to_bits()), LayerMask::ALL),
|
||||||
CollisionEventsEnabled,
|
CollisionEventsEnabled,
|
||||||
Sensor,
|
Sensor,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PointClass, Component, Reflect, Default)]
|
||||||
|
#[reflect(QuakeClass, Component)]
|
||||||
|
#[base(Transform)]
|
||||||
|
#[model({ "path": "models/head_drop.glb" })]
|
||||||
|
pub struct SecretHead {
|
||||||
|
pub head_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plugin(app: &mut App) {
|
||||||
|
app.register_type::<SpawnPoint>();
|
||||||
|
app.register_type::<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>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,16 +10,20 @@ pub struct Trail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Trail {
|
impl Trail {
|
||||||
pub fn new(pos: Vec3, col_start: LinearRgba, col_end: LinearRgba) -> Self {
|
pub fn new(points: usize, col_start: LinearRgba, col_end: LinearRgba) -> Self {
|
||||||
let mut v = Vec::with_capacity(12);
|
|
||||||
v.push(pos);
|
|
||||||
Self {
|
Self {
|
||||||
points: v,
|
points: Vec::with_capacity(points),
|
||||||
col_start,
|
col_start,
|
||||||
col_end,
|
col_end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_pos(self, pos: Vec3) -> Self {
|
||||||
|
let mut trail = self;
|
||||||
|
trail.add(pos);
|
||||||
|
trail
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, pos: Vec3) {
|
pub fn add(&mut self, pos: Vec3) {
|
||||||
if self.points.len() >= self.points.capacity() {
|
if self.points.len() >= self.points.capacity() {
|
||||||
self.points.pop();
|
self.points.pop();
|
||||||
@@ -36,37 +40,31 @@ pub fn plugin(app: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_trail(
|
fn update_trail(
|
||||||
mut query: Query<(&mut Trail, &Gizmo, &GlobalTransform, &ChildOf)>,
|
mut query: Query<(Entity, &mut Trail, &Gizmo, &GlobalTransform)>,
|
||||||
global_transform: Query<&GlobalTransform>,
|
global_transform: Query<&GlobalTransform>,
|
||||||
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
|
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
|
||||||
) -> Result<(), BevyError> {
|
) -> Result {
|
||||||
for (mut trail, gizmo, pos, parent) in query.iter_mut() {
|
for (e, mut trail, gizmo, pos) in query.iter_mut() {
|
||||||
trail.add(pos.translation());
|
trail.add(pos.translation());
|
||||||
|
|
||||||
let parent_transform = global_transform.get(parent.parent())?;
|
let parent_transform = global_transform.get(e)?;
|
||||||
|
|
||||||
let Some(gizmo) = gizmo_assets.get_mut(gizmo.handle.id()) else {
|
let Some(gizmo) = gizmo_assets.get_mut(gizmo.handle.id()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let lerp_denom = trail.points.len() as f32;
|
gizmo.clear();
|
||||||
for (i, window) in trail.points.windows(2).enumerate() {
|
|
||||||
let [a, b] = window else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let a = GlobalTransform::from_translation(*a)
|
|
||||||
.reparented_to(parent_transform)
|
|
||||||
.translation;
|
|
||||||
let b = GlobalTransform::from_translation(*b)
|
|
||||||
.reparented_to(parent_transform)
|
|
||||||
.translation;
|
|
||||||
|
|
||||||
gizmo.line(
|
let lerp_denom = trail.points.len() as f32;
|
||||||
a,
|
|
||||||
b,
|
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),
|
trail.col_start.mix(&trail.col_end, i as f32 / lerp_denom),
|
||||||
);
|
)
|
||||||
}
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"materials": {
|
"materials": {
|
||||||
"root": "textures",
|
"root": "textures",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
".D",
|
|
||||||
"png"
|
"png"
|
||||||
],
|
],
|
||||||
"palette": "palette.lmp",
|
"palette": "palette.lmp",
|
||||||
|
|||||||
@@ -1,85 +1,50 @@
|
|||||||
@BaseClass = bsp_solid_entity
|
@BaseClass = __target
|
||||||
[
|
[
|
||||||
_lmscale(integer) : "_lmscale" : : "Generates an `LMSHIFT` BSPX lump for use by a light util. Note that both scaled and unscaled lighting will normally be used."
|
target(string) : "target" : "" : "If [`Some`], when this entity's IO fires, it will activate all entities with its [`Targetable::targetname`] set to this, with whatever input that functionality that entity has set up."
|
||||||
_mirrorinside(integer) : "_mirrorinside" : 0 : "Set to 1 to save mirrored inside faces for brush models, so when the player view is inside the model, they will still see the faces. (e.g. for func_water, or func_illusionary)"
|
killtarget(string) : "killtarget" : "" : "If [`Some`], when this entity's IO fires, it will kill all entities with its [`Targetable::targetname`] set to this."
|
||||||
_chop_order(integer) : "_chop_order" : 0 : "Customize the brush order, which affects which brush “wins” in the CSG phase when there are multiple overlapping brushes, since most .map editors don’t directly expose the brush order.
|
|
||||||
Defaults to 0, brushes with higher values (equivalent to appearing later in the .map file) will clip away lower valued brushes."
|
|
||||||
_hulls(integer) : "_hulls" : 0 : "Bitmap (“Flags” type in FGD) that selects for which hulls collision data will be generated. eg. a decimal value of 11 (0b1011) would generate hull 0, hull 1, and hull 3. Faces are computed using data from hull 0, not generating this hull will prevent a brush model from being rendered, acting as a CLIP brush only active for the specified hulls.
|
|
||||||
Defaults to 0 which will generate clipnodes for all hulls."
|
|
||||||
_minlight(float) : "_minlight" : "0" : "`worldspawn`: Set a global minimum light level of this value across the whole map. This is an easy way to eliminate completely dark areas of the level, however you may lose some contrast as a result, so use with care. Default 0.
|
|
||||||
`model entity`: Set the minimum light level for any surface of the brush model. Default 0."
|
|
||||||
_minlight_mottle(integer) : "_minlight_mottle" : 0 : "Whether minlight should have a mottled pattern. Defaults to 0."
|
|
||||||
_minlight_color(color1) : "_minlight_color" : "255 255 255" : "Specify red(r), green(g) and blue(b) components for the colour of the minlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light (''255 255 255'')."
|
|
||||||
_minlight_exclude(string) : "_minlight_exclude" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
|
|
||||||
_minlight_exclude2(string) : "_minlight_exclude2" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
|
|
||||||
_minlight_exclude3(string) : "_minlight_exclude3" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
|
|
||||||
_shadow(integer) : "_shadow" : 0 : "If set to 1, this model will cast shadows on other models and itself (i.e. ''_shadow'' implies ''_shadowself''). Note that this doesn’t magically give Quake dynamic lighting powers, so the shadows will not move if the model moves. Set to -1 on func_detail/func_group to prevent them from casting shadows. Default 0."
|
|
||||||
_shadowself(integer) : "_shadowself" : 0 : "If set to 1, this model will cast shadows on itself if one part of the model blocks the light from another model surface. This can be a better compromise for moving models than full shadowing. Default 0."
|
|
||||||
_shadowworldonly(integer) : "_shadowworldonly" : 0 : "If set to 1, this model will cast shadows on the world only (not other brush models)."
|
|
||||||
_switchableshadow(integer) : "_switchableshadow" : 0 : "If set to 1, this model casts a shadow that can be switched on/off using QuakeC. To make this work, a lightstyle is automatically assigned and stored in a key called ''switchshadstyle'', which the QuakeC will need to read and call the ''lightstyle()'' builtin with ''a'' or ''m'' to switch the shadow on or off. Entities sharing the same targetname, and with ''_switchableshadow'' set to 1, will share the same lightstyle."
|
|
||||||
_dirt(integer) : "_dirt" : 0 : "`worldspawn`: 1 enables dirtmapping (ambient occlusion) on all lights, borrowed from q3map2. This adds shadows to corners and crevices. You can override the global setting for specific lights with the ''_dirt'' light entity key or ''_sunlight_dirt'', ''_sunlight2_dirt'', and ''_minlight_dirt'' worldspawn keys. Default is no dirtmapping (-1).
|
|
||||||
`model entity`: For brush models, -1 prevents dirtmapping on the brush model. Useful it the brush model touches or sticks into the world, and you want to those ares from turning black. Default 0."
|
|
||||||
_phong(integer) : "_phong" : 0 : "1 enables phong shading on this model with a default _phong_angle of 89 (softens columns etc)."
|
|
||||||
_phong_angle(float) : "_phong_angle" : "89" : "Enables phong shading on faces of this model with a custom angle. Adjacent faces with normals this many degrees apart (or less) will be smoothed. Consider setting ''_anglescale'' to ''1'' on lights or worldspawn to make the effect of phong shading more visible. Use the ''-phongdebug'' command-line flag to save the interpolated normals to the lightmap for previewing (use ''r_lightmap 1'' or ''gl_lightmaps 1'' in your engine to preview.)"
|
|
||||||
_phong_angle_concave(float) : "_phong_angle_concave" : "" : "Optional key for setting a different angle threshold for concave joints. A pair of faces will either use ''_phong_angle'' or ''_phong_angle_concave'' as the smoothing threshold, depending on whether the joint between the faces is concave or not. ''_phong_angle(_concave)'' is the maximum angle (in degrees) between the face normals that will still cause the pair of faces to be smoothed. The minimum setting for ''_phong_angle_concave'' is 1, this should make all concave joints non-smoothed (unless they’re less than 1 degree apart, almost a flat plane.) If it’s 0 or unset, the same value as ''_phong_angle'' is used."
|
|
||||||
_phong_group(integer) : "_phong_group" : 0 : "Integer specifying a “smoothing group ID” for phong shading. Default 0, faces with a _phong_group will only smooth with faces with a matching _phong_group.
|
|
||||||
Equivalent to the Q2 .map format’s “value” field."
|
|
||||||
_lightignore(integer) : "_lightignore" : 0 : "1 makes a model receive minlight only, ignoring all lights / sunlight. Could be useful on rotators / trains."
|
|
||||||
_light_twosided(integer) : "_light_twosided" : : "Set to 1 to enable receiving light from either side.
|
|
||||||
Default is 0 execept on liquids (Q1 *, Q2 contents LAVA/SLIME/WATER), where it defaults to 1."
|
|
||||||
_light_alpha(float) : "_light_alpha" : "" : "Float, range 0-1. Allows customizing the opacity of this face when it’s acting as “stained glass”.
|
|
||||||
`ericw-tools todo` Document default, and which conditions cause a face to be “stained glass”"
|
|
||||||
_litwater(integer) : "_litwater" : : "Overrides the worldspawn/command line option qbsp -litwater for these specific brushes."
|
|
||||||
_surflight_atten(float) : "_surflight_atten" : "" : "Overrides the worldspawn key _surflight_atten for these brushes."
|
|
||||||
_surflight_rescale(integer) : "_surflight_rescale" : : "Integer, 0 or 1.
|
|
||||||
If 1, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface normal. Otherwise, use a more natural angle falloff of 0% at 90 degrees.
|
|
||||||
Default is 0 on sky faces, otherwise 1."
|
|
||||||
_surflight_color(color1) : "_surflight_color" : "" : "Customize the emissive color of a surface light.
|
|
||||||
Default is to use the average texture color."
|
|
||||||
_surflight_style(integer) : "_surflight_style" : : "Override the surface light lightstyle number for light emitted from these brushes."
|
|
||||||
_surflight_targetname(string) : "_surflight_targetname" : "" : "Override the surface light targetname for light emitted from these brushes."
|
|
||||||
_surflight_minlight_scale(float) : "_surflight_minlight_scale" : "" : "Overrides the worldspawn setting `_surflight_minlight_scale`."
|
|
||||||
_bounce(integer) : "_bounce" : 0 : "Set to -1 to prevent this model from bouncing light (i.e. prevents its brushes from emitting bounced light they receive from elsewhere.) Only has an effect if “_bounce” is enabled in worldspawn."
|
|
||||||
_autominlight(integer) : "_autominlight" : 0 : "“Autominlight” is a feature for automatically choosing a suitable minlight color for a solid entity (e.g. a func_door), by averaging incoming light at the center of the model bounding box.
|
|
||||||
Default behaviour is to apply autominlight on occluded luxels only (e.g., for a door that opens vertically upwards, it would apply to the bottom face of the door, which is initially pressed against the ground).
|
|
||||||
A value of “-1” disables the feature (occluded luxels will be solid black), and “1” enables it as a minlight color even on non-occluded luxels."
|
|
||||||
_autominlight_target(string) : "_autominlight_target" : "" : "For autominlight, instead of using the center of the model bounds as the sample point, searches for an entity with its “targetname” key set to “name”, and use that entity’s origin (typically you’d use an “info_null” for this)."
|
|
||||||
_world_units_per_luxel(float) : "_world_units_per_luxel" : "" : "When -world_units_per_luxel is in use, customizes the lightmap scale on this entity."
|
|
||||||
_surflight_group(integer) : "_surflight_group" : 0 : "Integer. Default 0.
|
|
||||||
Can be set to a nonzero value to make these brushes emit as surface lights only from a light template with a matching _surflight_group value."
|
|
||||||
_lightcolorscale(float) : "_lightcolorscale" : "1" : "Saturation control as a postprocessing step on these specific faces’ lightmaps.
|
|
||||||
Default 1.0, 0.0 is fully desaturated to greyscale."
|
|
||||||
_object_channel_mask(integer) : "_object_channel_mask" : 1 : "Mask of lighting channels that this bmodel receives light on, blocks light on, and tests for AO on.
|
|
||||||
Default 1.
|
|
||||||
NOTE: Changing this from 1 will disable bouncing light off of this bmodel.
|
|
||||||
NOTE: Changing this from 1 implicitly enables _shadow.
|
|
||||||
NOTE: Changing to 2, for example, will cause the bmodel to initially be solid black. You’ll need to add minlight or lights with _light_channel_mask 2."
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) = camera_target
|
@BaseClass = __transform
|
||||||
|
[
|
||||||
|
origin(vector) : "Translation/Origin" : "0 0 0" : ""
|
||||||
|
angles(vector) : "Rotation (pitch yaw roll) in degrees" : "0 0 0" : ""
|
||||||
|
scale(vector) : "Scale" : "1 1 1" : ""
|
||||||
|
]
|
||||||
|
|
||||||
|
@BaseClass = __visibility
|
||||||
|
[
|
||||||
|
visibility(choices) : "Visibility" : "Inherited" : "" =
|
||||||
|
[
|
||||||
|
"Inherited" : "Uses the visibility of its parents. If its a root-level entity, it will be visible."
|
||||||
|
"Hidden" : "Always not rendered, regardless of its parent's visibility."
|
||||||
|
"Visible" : "Always rendered, regardless of its parent's visibility."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
@PointClass base(__transform) = camera_target
|
||||||
[
|
[
|
||||||
targetname(string) : "targetname" : "" : ""
|
targetname(string) : "targetname" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) model({ "path": "models/cash.glb" }) = cash_spawn
|
@PointClass base(__transform) model({ "path": "models/cash.glb" }) = cash_spawn
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
@SolidClass base(transform) = crates
|
@SolidClass base(__transform) = crates
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform, target) = cutscene_camera
|
@PointClass base(__transform, __target) = cutscene_camera
|
||||||
[
|
[
|
||||||
name(string) : "name" : "" : ""
|
name(string) : "name" : "" : ""
|
||||||
targetname(string) : "targetname" : "" : ""
|
targetname(string) : "targetname" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform, target) = cutscene_camera_movement_end
|
@PointClass base(__transform, __target) = cutscene_camera_movement_end
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) model({ "path": "models/alien_naked.glb" }) = enemy_spawn
|
@PointClass base(__transform) model({ "path": "models/alien_naked.glb" }) = enemy_spawn
|
||||||
[
|
[
|
||||||
head(string) : "head" : "" : ""
|
head(string) : "head" : "" : ""
|
||||||
key(string) : "key" : "" : ""
|
key(string) : "key" : "" : ""
|
||||||
@@ -90,58 +55,40 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@SolidClass base(transform, target) = movable
|
@SolidClass base(__transform, __target) = movable
|
||||||
[
|
[
|
||||||
name(string) : "name" : "" : ""
|
name(string) : "name" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) = move_target
|
@PointClass base(__transform) = move_target
|
||||||
[
|
[
|
||||||
targetname(string) : "targetname" : "" : ""
|
targetname(string) : "targetname" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@SolidClass base(transform) = named_entity
|
@SolidClass base(__transform) = named_entity
|
||||||
[
|
[
|
||||||
name(string) : "name" : "" : ""
|
name(string) : "name" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@SolidClass base(transform, target) = platform
|
@SolidClass base(__transform, __target) = platform
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) = platform_target
|
@PointClass base(__transform) = platform_target
|
||||||
[
|
[
|
||||||
targetname(string) : "targetname" : "" : ""
|
targetname(string) : "targetname" : "" : ""
|
||||||
]
|
]
|
||||||
|
|
||||||
@PointClass base(transform) model({ "path": "models/spawn.glb" }) = spawn_point
|
@PointClass base(__transform) model({ "path": "models/head_drop.glb" }) = secret_head
|
||||||
|
[
|
||||||
|
head_id(integer) : "head_id" : 0 : ""
|
||||||
|
]
|
||||||
|
|
||||||
|
@PointClass base(__transform) model({ "path": "models/spawn.glb" }) = spawn_point
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
@BaseClass = target
|
@SolidClass base(__transform) = water
|
||||||
[
|
|
||||||
target(string) : "target" : "" : "If [`Some`], when this entity's IO fires, it will activate all entities with its [`Targetable::targetname`] set to this, with whatever input that functionality that entity has set up."
|
|
||||||
killtarget(string) : "killtarget" : "" : "If [`Some`], when this entity's IO fires, it will kill all entities with its [`Targetable::targetname`] set to this."
|
|
||||||
]
|
|
||||||
|
|
||||||
@BaseClass = transform
|
|
||||||
[
|
|
||||||
origin(vector) : "Translation/Origin" : "0 0 0" : ""
|
|
||||||
angles(vector) : "Rotation (pitch yaw roll) in degrees" : "0 0 0" : ""
|
|
||||||
scale(vector) : "Scale" : "1 1 1" : ""
|
|
||||||
]
|
|
||||||
|
|
||||||
@BaseClass = visibility
|
|
||||||
[
|
|
||||||
visibility(choices) : "Visibility" : "Inherited" : "" =
|
|
||||||
[
|
|
||||||
"Inherited" : "Uses the visibility of its parents. If its a root-level entity, it will be visible."
|
|
||||||
"Hidden" : "Always not rendered, regardless of its parent's visibility."
|
|
||||||
"Visible" : "Always rendered, regardless of its parent's visibility."
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
@SolidClass = water
|
|
||||||
[
|
[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user